Search code examples
androidxamarin.formspermissions

Android 13: Xamarin.Forms doesn't give permissions to access gallery images


since Android 13, my Xamarin.Forms app no longer has the required permissions to access the images in the gallery. I know that Google has changed the access conditions and that we need to replace READ_EXTERNAL_STORAGE with Granular media permissions: READ_MEDIA_IMAGES, READ_MEDIA_VIDEO and READ_MEDIA_AUDIO. I've applied all these changes, but access to the images is still denied (I don't have a permission request from the system). Worse, when I manually assign permissions myself, it doesn't work either. Finally, it seems that the Xamarin.Essentials component (I'm on version 1.8.0) doesn't consider the changes imposed by Android 13. Is there an update planned? Is there a way around this? Here's my AndroidManifest.xml file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ApriSoft.memocourses" android:installLocation="auto" android:versionName="5.0.3" android:versionCode="503">
<uses-sdk android:minSdkVersion="20" android:targetSdkVersion="33" />
<application android:label="MemoCourses.Android">
    <activity android:name="microsoft.identity.client.BrowserTabActivity" android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="msauth" android:host="com.ApriSoft.memocourses" />
        </intent-filter>
    </activity>
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

And the MainActivity.cs file:

using System;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using static MemoCourses.Droid.MainActivity;
using Android.Content;
using Microsoft.Identity.Client;
using Xamarin.Essentials;
using MemoCourses.Models;
using System.Collections.ObjectModel;
using Android.Text.Method;
using Android.Text;
using Plugin.CurrentActivity;

[assembly: ExportRenderer(typeof(Entry), typeof(MyEntryRenderer))]
[assembly: UsesPermission(Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaImages)]/* CSS Document*/

namespace MemoCourses.Droid
{
    [Activity(Label = "MemoCourses", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Portrait)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;
            base.OnCreate(savedInstanceState);
            Rg.Plugins.Popup.Popup.Init(this);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

            LoadApplication(new App());
            App.AuthUIParent = this;
            Window.SetStatusBarColor(Android.Graphics.Color.Argb(255, 47, 56, 40));
            ButtonCircleRenderer.Init();
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
       global::ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
        {
            base.OnActivityResult(requestCode, resultCode, data);
        
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
        }

        public override void OnBackPressed()
        {
            Rg.Plugins.Popup.Popup.SendBackPressed(base.OnBackPressed);
        }
    }
}

This is the code I use to choose the image:

try
{
    FileData fileData = await CrossFilePicker.Current.PickFile();
    //
    var filepath = fileData.FilePath;
    //
    if (fileData == null)
        return; // user canceled file picking
    
    newimageByte = fileData.DataArray;
    resizedImageByte = await ImageResizer.ResizeImage(newimageByte, 200, 200);

    if (resizedImageByte != null)
    {
        var stream1 = new MemoryStream(resizedImageByte);
        ImageArticle.Source = ImageSource.FromStream(() => stream1);
    }
}
catch (Exception ex)
{
    
    imageGallery = false;
    imageChanged = false;
}

Thank you very much for helping me, I've been looking for several days, and I can't find a solution. All users of my app whose phone has been updated to Android 13 can no longer pick an image from the gallery.


Solution

  • I tested the method on my side and it worked well. You can use the codes below to upload the pictures on Android 13. Please put the code in the MainActivity class.

       public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
                {
                    if (Xamarin.Essentials.DeviceInfo.Version.Major >= 13 && (permissions.Where(p => p.Equals("android.permission.WRITE_EXTERNAL_STORAGE")).Any() || permissions.Where(p => p.Equals("android.permission.READ_EXTERNAL_STORAGE")).Any()))
                    {
                        var wIdx = Array.IndexOf(permissions, "android.permission.WRITE_EXTERNAL_STORAGE");
                        var rIdx = Array.IndexOf(permissions, "android.permission.READ_EXTERNAL_STORAGE");
        
                        if (wIdx != -1 && wIdx < permissions.Length) grantResults[wIdx] = Permission.Granted;
                        if (rIdx != -1 && rIdx < permissions.Length) grantResults[rIdx] = Permission.Granted;
                    }
        
                    Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
                    PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
                }
    

    I made a demo, and you can try to use this to get the image form the photo by using the Xamarin.Essentials: Media Picker.

    Here is the code in Mainpage.xaml.

     <Image x:Name="FotoProduto"></Image>
     <Button x:Name="button" Text="button" Clicked="button_Clicked"></Button>
    

    The code in the Mainpage.xaml.cs.

    private async void button_Clicked(object sender, EventArgs e)
            {
                var result = await MediaPicker.PickPhotoAsync(new MediaPickerOptions
                {
                    Title = "Choise the product photo"
                });
    
                var stream = await result.OpenReadAsync();
    
                FotoProduto.Source = ImageSource.FromStream(() => stream);
    
    
            }
    

    Update method in Mainpage.xaml.cs

    Do not use the stream way.

      var result = await MediaPicker.PickPhotoAsync(new MediaPickerOptions
                {
                    Title = "Choise the product photo"
                });
                var newFile = Path.Combine(FileSystem.CacheDirectory, result.FileName);
    
                FotoProduto.Source = newFile;