androidexcelandroid-intentxamarin.androidandroid-intent-chooser

Xamarin.Android: Opening an Excel document using Intents in Android 10+ fails


I have the following code for exporting an Excel file. The final result is opened in the Excel App. This is the code for opening the file:

if (file.Exists())
    Intent intent = new Intent(Intent.ActionView);

    if (Build.VERSION.SdkInt < BuildVersionCodes.N)
    {
        intent.SetDataAndType(Android.Net.Uri.FromFile(file), MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));
    }
    else
    {
        intent.AddFlags(ActivityFlags.GrantReadUriPermission);
        intent.SetDataAndType(Android.Net.Uri.Parse(file.Path), MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));
    }
    context.StartActivity(Intent.CreateChooser(intent, context.GetString(Resource.String.LblChooseApp)));
}

I know the file is created because I don't get any error and reaches this point properly. Also, I added the flag FLAG_GRANT_READ_URI_PERMISSION/GrantReadUriPermission already and when I try to open the file, I get this message:

Can't open file | Try saving the file on the device and then opening it

This is part of my AndroidManifest.xml

<application android:theme="@style/Launcher" android:icon="@drawable/icon" android:label="@string/app_name" android:requestLegacyExternalStorage="true">
    <activity android:name="tk.supernova.tmtimer.MainActivity" android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
        <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" />
    </activity>
</application>

And I already requested the Read/Write permissions:

private readonly string[] PermissionToCheck = {
    Android.Manifest.Permission.WriteExternalStorage,
    Android.Manifest.Permission.ReadExternalStorage
};
private const int REQUEST_ID = 0;
private const int STORAGE_PERMISSION_CODE = 23;

protected override void OnCreate(Bundle savedInstanceState)
{
    Platform.Init(this, savedInstanceState);
    if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
    {
        RequestPermissions(PermissionToCheck, REQUEST_ID);
    }

    if (Build.VERSION.SdkInt >= BuildVersionCodes.N)
    {
        try
        {
            StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
                                .PenaltyDeathOnFileUriExposure()
                                .DetectFileUriExposure()
                                .Build();
            StrictMode.SetVmPolicy(policy);
        }
        catch { }
    }
}

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
    base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

Any idea what am I doing wrong? Thanks.


Solution

  • Based on @blackapps suggestions, I did the following changes.

    #1. To add a new section in the AndroidManifest.xml:

    <provider android:name="androidx.core.content.FileProvider" android:authorities="tk.supernova.tmtimer.tk.supernova.tmtimer.fileprovider" android:exported="false" android:grantUriPermissions="true">
        <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
    </provider>
    

    #2. To create a new file in the XML folder called file_paths that contains:

    <?xml version="1.0" encoding="UTF-8" ?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <files-path name="my_meetings" path="meetings/" />
    </paths>
    

    #3. And this changes to open and save the file correctly:

    private File CreateDirFile(string fileName)
    {
        var meetingPath = new File(Xamarin.Essentials.FileSystem.AppDataDirectory, "meetings");
        meetingPath.Mkdir();
        var file = new File(meetingPath, fileName);
    
        if (file.Exists())
        {
            file.Delete();
        }
    
        file.CreateNewFile();
        return file;
    }
    
    private void Save(string fileName, string contentType, MemoryStream stream, Context context)
    {
        var file = CreateDirFile(fileName);
        try
        {
            FileOutputStream outs = new FileOutputStream(file, false);
            outs.Write(stream.ToArray());
            outs.Flush();
            outs.Close();
        }
        catch
        {
            Toast.MakeText(context, context.GetString(Resource.String.LblStorageIssue), ToastLength.Long).Show();
        }
        if (file.Exists() && contentType != APP_TYPE)
        {
            Intent intent = new Intent(Intent.ActionView);
    
            if (Build.VERSION.SdkInt < BuildVersionCodes.N)
            {
                intent.SetDataAndType(Android.Net.Uri.FromFile(file), MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));
            }
            else
            {
                intent.AddFlags(ActivityFlags.GrantReadUriPermission);
                intent.AddFlags(ActivityFlags.GrantWriteUriPermission);
    
                var contentUri = FileProvider.GetUriForFile(Application.Context, FILE_PROVIDER, file);
    
                intent.SetDataAndType(contentUri, MimeTypeMap.Singleton.GetMimeTypeFromExtension(MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString())));
            }
            context.StartActivity(Intent.CreateChooser(intent, context.GetString(Resource.String.LblChooseApp)));
        }
    }
    

    #4. I removed the strict section.