Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

Adds Granular media permissions for Android 13+ #2065

Closed
wants to merge 1 commit into from

Conversation

Jon2G
Copy link

@Jon2G Jon2G commented Dec 4, 2022

Adds Granular media permissions for Android 13+
https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions

Description of Change

Adds Granular media permissions for Android 13+
Starting from Android 13 you can no longer request STORAGE_READ, and STORAGE_WRITE permissions.
Instead, you have to ask for granular permissions:
READ_MEDIA_IMAGES
READ_MEDIA_VIDEO
READ_MEDIA_AUDIO

Bugs Fixed

  • Related to issue #

Provide links to issues here. Ensure that a GitHub issue was created for your feature or bug fix before sending PR.

#2037
#2041

API Changes

None

Behavioral Changes

None

PR Checklist

  • Has tests
  • Has samples
  • Rebased on top of main at time of PR
  • Changes adhere to coding standard
  • Updated documentation (see walkthrough)

@Jon2G
Copy link
Author

Jon2G commented Dec 4, 2022

@microsoft-github-policy-service agree

@jfversluis jfversluis changed the title Fixes Issues #2041,#2037 Adds Granular media permissions for Android 13+ Dec 5, 2022
{
return new (string, bool)[]
{
("android.permission.READ_MEDIA_IMAGES", true),
Copy link

@IeuanWalker IeuanWalker Dec 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will probably be needed to spit out into their own permissions.

My app only needs access to the users images, I don't want to add/request for 'Audio'/'Video' permissions when it's not needed

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<!--Android 13 specific media folders-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" android:minSdkVersion="32"/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

android:minSdkVersion for these READ_MEDIA_* permissions should be 33 since they were only added in Android API 33 (Android 13). See here: https://developer.android.com/reference/android/Manifest.permission#READ_MEDIA_AUDIO

</queries>
<application android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/MainTheme"></application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.xamarin.essentials" android:installLocation="auto">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the device tests to test features in Android API 33, the target version surely needs to be at least 33 here?

@simader
Copy link

simader commented Jan 10, 2023

please merge

@daflotsch
Copy link

Please merge this PR, our customers already complain a lot

@simader
Copy link

simader commented Jan 17, 2023

Any news on this? Our app is useless in Android 13 because of this bug. Please merge and publish as soon as possible.

@jfversluis
Copy link
Member

@simader @daflotsch you can easily implement this yourself by extending the current permissions as described in our documentation: https://learn.microsoft.com/xamarin/essentials/permissions?tabs=android#extending-permissions

As mentioned in this PR also about permissions, Xamarin.Essentials is in maintanance only and we're not looking to add new functionality unless we absolutely have to. New development will happen in .NET MAUI.

@jfversluis jfversluis closed this Feb 14, 2023
@WanftMoon
Copy link

@jfversluis

I was under the impression that when you guys said that Xamarin would support up to android 13 (even under maintenance), this kind of issue would still be addressed.
But thank you for clarifying it.

@Kukulkano
Copy link

I just have to deal with permission issues in a 5 years old Xamarin app. Also facing pressure for release as it has to support a new product.

To my knowledge, Xamarin EOL date is currently May 1, 2024. Therefore, I do not understand statements like "Xamarin.Essentials is in maintanance only and we're not looking to add new functionality unless we absolutely have to.". This is not acceptable. We need running Xamarin products until this date. And if Android or iOS are making changes that break general usability, this is a "have to". I wonder if others see it the same way...

@WanftMoon
Copy link

WanftMoon commented Jun 23, 2023

@Kukulkano

/agree

While they don't move, this is what you can do to make it work.

  1. add permissions to manifest
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
  1. Extend BasePlatformPermission
 public class ReadImagesVideoPermission : Xamarin.Essentials.Permissions.BasePlatformPermission, IReadImagesVideoPermission
    {      
        public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string androidPermission, bool isRuntime)> {
            ("android.permission.READ_MEDIA_IMAGES", true),
            ("android.permission.READ_MEDIA_VIDEO", true)
        }.ToArray();

    }
  1. In my case, i had to trick xam essentials in thinking the old permissions (ex: read_external_storage) were granted in android 13.
 public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.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;
            }

            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
  1. Call the dependency service
if (DeviceInfo.Platform.Equals(DevicePlatform.Android) && DeviceInfo.Version.Major >= 13)
            {
                var readImagesVideoPermission = DependencyService.Get<IReadImagesVideoPermission>();

                var permission = await readImagesVideoPermission.CheckStatusAsync();

                if (permission != PermissionStatus.Granted)
                {
                    // Prompt the user with additional information as to why the permission is needed
                    if (rationale != null) await rationale();

                    permission = await readImagesVideoPermission.RequestAsync();
                }

                return permission;
            }

@zerokewl88
Copy link

Hi @WanftMoon really thank you for your solution. In my case, i only had to do step 3 of your suggestion in order to ge tthis to work.

I changed the OnRequestPermissionResult, and added the lines you suggested, and also updated my manifest - and it works. I did not have to implement a Interface (i think this is because my manifest already included the new permissions.

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.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;
            }

            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }

Manifest

	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.CAMERA" />
	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
	<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
	<uses-feature android:name="android.hardware.location" android:required="false" />
	<uses-feature android:name="android.hardware.location.gps" android:required="false" />
	<uses-feature android:name="android.hardware.location.network" android:required="false" />
  
  <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
  <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>

@willisra83
Copy link

willisra83 commented Sep 29, 2023

@Kukulkano

/agree

While they don't move, this is what you can do to make it work.

  1. add permissions to manifest
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
  2. Extend BasePlatformPermission
    public class ReadImagesVideoPermission : Xamarin.Essentials.Permissions.BasePlatformPermission, IReadImagesVideoPermission { public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string androidPermission, bool isRuntime)> { ("android.permission.READ_MEDIA_IMAGES", true), ("android.permission.READ_MEDIA_VIDEO", true) }.ToArray(); }
  3. In my case, i had to trick xam essentials in thinking the old permissions (ex: read_external_storage) were granted in android 13.
    ` public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.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;
         }
    
         Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
         base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
     }`
    
  4. Call the dependency service
    ` if (DeviceInfo.Platform.Equals(DevicePlatform.Android) && DeviceInfo.Version.Major >= 13)
    {
    var readImagesVideoPermission = DependencyService.Get();
             var permission = await readImagesVideoPermission.CheckStatusAsync();
    
             if (permission != PermissionStatus.Granted)
             {
                 // Prompt the user with additional information as to why the permission is needed
                 if (rationale != null) await rationale();
    
                 permission = await readImagesVideoPermission.RequestAsync();
             }
    
             return permission;
         }`
    

Will this still work with a min SDK of <33 specified? Or do both min and target SDK have to be 33? And so should the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE entries in the manifest just stay as there are or be removed? Our min SDK is version 27 and so I'm wondering if we need to require users to go to 33...

@WanftMoon
Copy link

WanftMoon commented Sep 29, 2023

Will this still work with a min SDK of <33 specified? Or do both min and target SDK have to be 33? And so should the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE entries in the manifest just stay as there are or be removed?

Yeah, it will. My mininum is 26 right now and target is 33. And you need those permissions to devices below 33.

@willisra83
Copy link

Will this still work with a min SDK of <33 specified? Or do both min and target SDK have to be 33? And so should the READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE entries in the manifest just stay as there are or be removed?

Yeah, it will. My mininum is 26 right now and target is 33.

So your manifest simply has all these entries (without specifying "minSdkVersion" and "maxSdkVersion" attributes)?

        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"  />
	<uses-permission  android:name="android.permission.READ_MEDIA_IMAGES"/>
	<uses-permission  android:name="android.permission.READ_MEDIA_VIDEO"/>
         <uses-permission android:name="android.permission.CAMERA" />

@WanftMoon
Copy link

So your manifest simply has all these entries (without specifying "minSdkVersion" and "maxSdkVersion" attributes)?

yup

<uses-sdk android:minSdkVersion="26" android:targetSdkVersion="33" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

@willisra83
Copy link

So your manifest simply has all these entries (without specifying "minSdkVersion" and "maxSdkVersion" attributes)?

yup

<uses-sdk android:minSdkVersion="26" android:targetSdkVersion="33" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

You are the man! Thanks.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants