Open ChrisDox opened 2 years ago
Hi guys, i have analyzed upgrade Android app's to API 33. So i want to clarify some aspect that i have met and hope to be of help to all of you.
ANDROID If your app targets Android 13 or higher and needs to access media files that other apps have created, you must request one or more of the following granular media permissions instead of the READ_EXTERNAL_STORAGE permission: READ_MEDIA_IMAGES READ_MEDIA_VIDEO READ_MEDIA_AUDIO
XAMARIN So if you need READ_EXTERNAL_STORAGE in your app you must replace it with new granular permissions.
Warning! if you need a simple Photo, Video or File picker, check if you really need permission -> READ_EXTERNAL_STORAGE https://stackoverflow.com/questions/72586826/is-permission-needed-when-choosing-image-from-gallery-on-android
So if you must use READ_EXTERNAL_STORAGE: 1)Xamarin.Essentials will never supported new Granular media permissions as says this topic -> https://github.com/xamarin/Essentials/pull/2065
So you must implement yourself this new three permissions, following this guidelines: https://learn.microsoft.com/en-us/xamarin/essentials/permissions?tabs=android#extending-permissions
SharedProject 1)Write general permission interface
public interface ICustomPermission
{
(string androidPermission, bool isRuntime)[] RequiredPermissions { get; }
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
2) inheritance from ICustomPermission for every new permission
public interface IReadMediaImagesPermission : ICustomPermission
{
}
AndroidProject 3) Define the follow class for every permission, and implement interface
[assembly: Xamarin.Forms.Dependency(typeof(<YourAndroidNameSpace>.Droid.ReadMediaImagesPermission))]
namespace <YourAndroidNameSpace>.Droid
{
public class ReadMediaImagesPermission : BasePlatformPermission, IReadMediaImagesPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new (string, bool)[] { (Manifest.Permission.ReadMediaImages, true) };
}
}
}
SharedProject 4) Use DependencyService for use new permissions as follow
var readMediaImagePermission = DependencyService.Get<IReadMediaImagesPermission>();
var status = await readMediaImagePermission.CheckStatusAsync();
if (status != PermissionStatus.Granted)
{
status = await readMediaImagePermission.RequestAsync();
}
In this case you only update Xamarin.Essentials v1.7.5. -> https://github.com/xamarin/Essentials/pull/2073
You don't need to add new granular permissions to Android manifests. Xamarin.Essentials Media Picker only need CAMERA permissions.
As Android developer docs says: https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE _Note: If your app targets Build.VERSION_CODES.R or higher, this permission has no effect._
So from API 33 you MUST stop to ask WRITE_EXTERNAL_STORAGE permission. if you can review your code, ok or you can use the @Pastajello hack.
Thanks for the explanation @giuseppenovielli.
I can now call newly introduced permissions that are not being handled automatically.
Extending permissions help me achieve requesting the permissions.
@gjhdigital @alexkivikoski
Ok so listen here, how about a little trick here: Since on API 33 the WRITE_MEDIA_STORAGE is useless the OS is ignoring it-> setting the permission status as denied. If we only have to internally trick the Essentials to think it is granted it is not THAT hard, is it? There is a place to tell Essentials what is the status of requested permissions. We just have to tell some lies.
Of course you need to remember that if there are multiple permissions in the array you have to replace only the value for the WRITE_MEDIA_STORAGE. AND check if we are on API 33+, before I think the permission is doing something, things will get messy otherwise I guess. But otherwise its working. ;) Just tested. (really this hack took me like 5minutes to think of)
`public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults) { if (permissions.ToList().Where(x => x == "WRITE_MEDIA_STORAGE") != null) { var grants= new Permission[] { Permission.Granted }; Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grants); } else { Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults); } base.OnRequestPermissionsResult(requestCode, permissions, grantResults); }`
I noticed a couple of corrections here, which may be useful:
@runtimesoftware , can you give a working example?
Hi guys, I landed on this thread because I kept getting an unauthorized access exception when trying to write even to internal app data on Android 13. So, I'm just posting my answer here in case anyone else has a similar issue. Hope it helps!
For API33:
READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE are not used anymore
AND(more importantly)-
For example, when you call requestPermissions() for WRITE_EXTERNAL_STORAGE onRequestPermissionResult() will always return PERMISSION_DENIED Long story short, this is the correct and expected behavior for API33.
To prevent this on API33:
Change AndroidManifest.xml
From:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
To:
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
This means when this app is installed on an Android device with an API higher than 18 (ie API33) the READ/WRITE_EXTERNAL_STORAGE permissions will be ignored as if it they were not even in the AndroidManifest.xml file.
Side note: Instead of maxSdkVersion="18", you could also choose "23" or even "32" I don't know what your xamarin/essentials software is but: This number depends on how your team is using READ/WRITE_EXTERNAL_STORAGE for previous versions of Android.
As Xamarin.Forms is going to support Android 13 (a prerelease doing so is available at https://github.com/xamarin/Xamarin.Forms/releases/tag/beta-5.0.0-sr15-pre1), I think this issue should be reevaluated.
The bug remains and the pre release not fixing the issue
@toumir Totally agree. My point was, that as Xamarin.Forms not starts to support Android 13, we should get the chance to include the Android 13 permission stuff into Xamarin.Essentials.
dont you just have to do this inthe Android. AssemblyInfo.cs file?
[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)] [assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
I dont see that being a Xamarin.Essentials thing. That would be like having Essentials add the ios infp.plist stuff.
Have you actually tried the essentials 1.8 preview build with android 13 support changes? It was released at the same time as the xamarin forms preview...
Still getting the same issue, xamarin.essentials.permissionexception: storageread permission was not granted: denied android api level 33 filepicker Please, let me know if any other solution for same.
Same issue here with camera, 1 - if I select (Don't allow permission) with Xamarin Essentials RequestPermission I get a Granted (very strange)
2 - If I use MediaPicker.CapturePhotoAsync(), app asking permissions and I select Don't allow option, app crash and I get this message (xamarin.essentials.permissionexception: storageread permission was not granted: denied android).
This happend with Android 13 only.
Same issue here, we request a permission from essentials and show granted, but when the app save on the disk we receive permission denied for storage write.
have any workaround to fix this?
We are still using Xamarin Forms.
I already try to put the granular permissions on manifest but still not working.
I have had this same problem and I fixed it by adding the below code in the manifest file for Android.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_VIDEO"/>
I have had this same problem and I fixed it by adding the below code in the manifest file for Android.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_IMAGES"/> <uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_VIDEO"/>
You didn't need to change anything else?
I have had this same problem and I fixed it by adding the below code in the manifest file for Android.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_IMAGES"/> <uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_VIDEO"/>
You didn't need to change anything else?
I have added another small check while checking the permission.
var permissionsNeeded = ['android.permission.READ_MEDIA_IMAGES', 'android.permission.READ_MEDIA_VIDEO'];
if (Ti.Platform.version < 13) {
permissionsNeeded = ['android.permission.READ_EXTERNAL_STORAGE', 'android.permission.WRITE_EXTERNAL_STORAGE'];
}
if (Ti.Android.hasPermission(permissionsNeeded)) {
//somelogic
} else {
Ti.Android.requestPermissions(permissionsNeeded, function (e) {
// Some custom logic of our app
});
}
@KamalKant-Tech - when adding your manifest to a API33, and then trying to access the permissions, i'm getting a exception - do you also get this ?
Manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_VIDEO"/>
Calling Code
if (Double.Parse(Xamarin.Essentials.DeviceInfo.VersionString) >= 13)
{
var nStatus = await Permissions.CheckStatusAsync<Permissions.StorageWrite>();
if (nStatus != PermissionStatus.Granted)
{
nStatus = await Permissions.RequestAsync<Permissions.StorageWrite>();
}
nStatus = await Permissions.CheckStatusAsync<Permissions.StorageRead>();
if (nStatus != PermissionStatus.Granted)
{
nStatus = await Permissions.RequestAsync<Permissions.StorageRead>();
}
}
Exception thrown
{Xamarin.Essentials.PermissionException: You need to declare using the permission: `android.permission.WRITE_EXTERNAL_STORAGE` in your AndroidManifest.xml
at Xamarin.Essentials.Permissions+BasePlatformPermission.CheckStatusAsync () [0x0003c] in D:\a\_work\1\s\Xamarin.Essentials\Permissions\Permissions.android.cs:64
at Xamarin.Essentials.Permissions.CheckStatusAsync[TPermission] () [0x00000] in D:\a\_work\1\s\Xamarin.Essentials\Permissions\Permissions.shared.cs:9
at ....InitPermissionsViewModel.<CheckPermissionTimer_Elapsed>b__22_0 () [0x000a5] in .... }
I did not get this error in my case.
This permission android.permission.WRITE_EXTERNAL_STORAGE
is not applicable for Android API 33 and above. Are you running this below 33 API levels?
@KamalKant-Tech thanks for the quick reply - i'm testing this again now, and ensure that it's API 33, and i'm still getting this exception.
{Plugin.Media.Abstractions.MediaPermissionException: Camera permission(s) are required.
at Plugin.Media.MediaImplementation.TakePhotoAsync (Plugin.Media.Abstractions.StoreCameraMediaOptions options, System.Threading.CancellationToken token) [0x0008f] in d:\a\1\s\src\Media.Plugin\Android\MediaImplementation.cs:189
at .....TakePhoto () [0x0016e] in ....}
@KamalKant-Tech thanks for the quick reply - i'm testing this again now, and ensure that it's API 33, and i'm still getting this exception.
{Plugin.Media.Abstractions.MediaPermissionException: Camera permission(s) are required. at Plugin.Media.MediaImplementation.TakePhotoAsync (Plugin.Media.Abstractions.StoreCameraMediaOptions options, System.Threading.CancellationToken token) [0x0008f] in d:\a\1\s\src\Media.Plugin\Android\MediaImplementation.cs:189 at .....TakePhoto () [0x0016e] in ....}
Add this permission to your manifest file.
<uses-permission android:name="android.permission.CAMERA"/>
@KamalKant-Tech Sorry, i should've given my complete manifest - i already have this included as you have suggested, and that error was still being thrown.
My complete manifest looks like this now...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="227" package="com.acme.org" android:installLocation="auto">
<uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
<application android:label="Induction" android:icon="@mipmap/icon" android:allowBackup="false">
<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:usesCleartextTraffic="true" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<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"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
</manifest>
@KamalKant-Tech By any chance are you using ?
Well, there seems to be a answer here at the bottom of this thread, and also some feedback from a member of Xamarin team, and their 'response' which was "As mentioned in https://github.com/xamarin/Essentials/pull/2063#issuecomment-1338106117 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."
OK - it's a simple fix to get around this, and it's working ok for me on API33.
You only have to update the following method, and then add the manifest entries - and your camera operations are working again on API 33.
Android Main Activity updat the following Method to this.. (This tricks Android to accept a permission on a platform where it's no longer used - lol )
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 File
<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"/>
Credit to @WanftMoon on this post (https://github.com/xamarin/Essentials/pull/2065) for pointing me in the right direction. Camera operations restored in API 33 - thank you.
OK - it's a simple fix to get around this, and it's working ok for me on API33.
You only have to update the following method, and then add the manifest entries - and your camera operations are working again on API 33.
1. Android Main Activity updat the following Method to this.. (This tricks Android to accept a permission on a platform where it's no longer used - lol )
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); }
2. Manifest File
<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"/>
Credit to @WanftMoon on this post (#2065) for pointing me in the right direction. Camera operations restored in API 33 - thank you.
Did you get this working in a release-version? It works for me, but only in debug. As if somethings wrong with the manifest-file.
I published it to a release version, assuming it would work.. after testing with a debug version. I think I'll report back here and double check it's working post release.. thanks.
Kind Regards,
Jeffrey Holmes +66806075331
From: Daniel Halme Ståhlberg @.> Sent: Friday, September 15, 2023 6:20:02 PM To: xamarin/Essentials @.> Cc: Jeffrey Holmes @.>; Comment @.> Subject: Re: [xamarin/Essentials] [Bug] Storage permissions Android 13 api 33 (Issue #2041)
OK - it's a simple fix to get around this, and it's working ok for me on API33.
You only have to update the following method, and then add the manifest entries - and your camera operations are working again on API 33.
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 File
<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" />
Credit to @WanftMoonhttps://github.com/WanftMoon on this post (#2065https://github.com/xamarin/Essentials/pull/2065) for pointing me in the right direction. Camera operations restored in API 33 - thank you.
Did you get this working in a release-version? It works for me, but only in debug. As if somethings wrong with the manifest-file.
— Reply to this email directly, view it on GitHubhttps://github.com/xamarin/Essentials/issues/2041#issuecomment-1721107768, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABJ4KG2FQD4UKZR2MGPHEFTX2Q2WFANCNFSM6AAAAAAQG6GEL4. You are receiving this because you commented.Message ID: @.***>
@leffemedkniven Hi there - i've just reinstalled a release version, and confirmed this method above works ok. Did you accept / create the permissions on startup ?
With what i have posted above, on startup, i have a check for permissions that requests the following.
try
{
cameraPermission = await CrossPermissions.Current.CheckPermissionStatusAsync<CameraPermission>();
if (cameraPermission != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var status = await CrossPermissions.Current.RequestPermissionAsync<CameraPermission>();
}
}
catch (Exception ex)
{
Crashes.TrackError(ex);
}
try
{
photosPermission = await CrossPermissions.Current.CheckPermissionStatusAsync<PhotosPermission>();
if (photosPermission != Plugin.Permissions.Abstractions.PermissionStatus.Granted)
{
var status = await CrossPermissions.Current.RequestPermissionAsync<PhotosPermission>();
}
}
catch (Exception ex)
{
Crashes.TrackError(ex);
}
cameraPermission = await CrossPermissions.Current.CheckPermissionStatusAsync
(); if (cameraPermission != Plugin.Permissions.Abstractions.PermissionStatus.Granted) { var status = await CrossPermissions.Current.RequestPermissionAsync (); }
When adding this to the PageModel it doesn't pop-up at all, only in debug. Trying to add it to MainActivity.OnCreate doesn't work because it's not async.
Tried this in MainActivity aswell:
if (ActivityCompat.CheckSelfPermission(Xamarin.Essentials.Platform.CurrentActivity, Android.Manifest.Permission.ReadMediaImages) != Permission.Granted) { ActivityCompat.RequestPermissions(Xamarin.Essentials.Platform.CurrentActivity, new string[] { Android.Manifest.Permission.ReadMediaImages }, 100); }
It pops up in release, I allow it, but it doesn't work when trying to add pictures to external storage.
I actually don't request permission for ReadMediaImages explicity during runtime, this is handled already in my manifest file without having to request it.
The permissions above that i'm requesting i've already included in the above post.
I have had this same problem and I fixed it by adding the below code in the manifest file for Android.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_IMAGES"/> <uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_VIDEO"/>
does this mean that we don't need permission to write in android 13 onwards? we can directly write if required? if yes than how is this increasing security?
I actually don't request permission for ReadMediaImages explicity during runtime, this is handled already in my manifest file without having to request it.
The permissions above that i'm requesting i've already included in the above post.
So in the Android manifest, did you end up commenting out the old permissions (READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE)? And so this means you no longer need to check CrossPermissions.Current.CheckPermissionStatusAsync<StoragePermission>()
on Android?
And you still need the code below also?
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);
}
Do you need any other changes to fix it? And know if this would still work with a min SDK of <33 specified? Or did you just specify both min SDK and target SDK as 33?
I can confirm that this works for 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);
}
but, I cannot remove the old permissions from the Manifest nor set android:maxSdkVersion= because the plugin will fail when calling CheckStatusAsync.
but, I cannot remove the old permissions from the Manifest nor set android:maxSdkVersion= because the plugin will fail when calling CheckStatusAsync.
Did you extend the permissions?
public interface IReadImagesVideoPermission
{
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
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();
}
//this is a check for android 13+
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;
}
In addition to the manifest changes, I found it simpler to create a subclass of StorageRead and put the sdk check there:
public class ReadMedia : Permissions.StorageRead
{
#if __ANDROID__
public override (string androidPermission, bool isRuntime)[] RequiredPermissions
{
get
{
// after sdk 33 (Android 13), this requires read two media values
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Tiramisu)
{
return new (string, bool)[] { ( Android.Manifest.Permission.ReadMediaImages, true), ( Android.Manifest.Permission.ReadMediaVideo, true) };
}
// otherwise, just use the original single permission read external storage
return base.RequiredPermissions;
}
}
#endif
}
Then use ReadMedia permission class anywhere I would use StorageRead.
but, I cannot remove the old permissions from the Manifest nor set android:maxSdkVersion= because the plugin will fail when calling CheckStatusAsync.
Did you extend the permissions?
public interface IReadImagesVideoPermission { Task<PermissionStatus> CheckStatusAsync(); Task<PermissionStatus> RequestAsync(); }
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(); }
//this is a check for android 13+ 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; }
Know when this base permission class functionality would/should be needed? Because with just simply using the code below, I'm able to upload photos on Android 13, 12, and 10 just fine. So it seems the above code isn't needed (unless I'm missing a scenario)...
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);
}
My manifest
<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" />
This worked for me
public class AllFileSystemPermission : Permissions.BasePermission
{
public override Task<PermissionStatus> CheckStatusAsync()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.R)
{
var readStatus = Permissions.CheckStatusAsync<Permissions.StorageRead>().Result;
var writeStatus = Permissions.CheckStatusAsync<Permissions.StorageWrite>().Result;
if (readStatus == PermissionStatus.Granted && writeStatus == PermissionStatus.Granted)
return Task.FromResult(PermissionStatus.Granted);
if (readStatus == PermissionStatus.Denied || writeStatus == PermissionStatus.Denied)
return Task.FromResult(PermissionStatus.Denied);
return Task.FromResult(PermissionStatus.Unknown);
}
else
{
var manageStatus = Platform.AppContext.CheckSelfPermission(Android.Manifest.Permission.ManageExternalStorage);
return Task.FromResult(manageStatus == Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied);
}
}
public override async Task<PermissionStatus> RequestAsync()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.R)
{
var readStatus = await Permissions.RequestAsync<Permissions.StorageRead>();
var writeStatus = await Permissions.RequestAsync<Permissions.StorageWrite>();
if (readStatus == PermissionStatus.Granted && writeStatus == PermissionStatus.Granted)
return PermissionStatus.Granted;
if (readStatus == PermissionStatus.Denied || writeStatus == PermissionStatus.Denied)
return PermissionStatus.Denied;
return PermissionStatus.Unknown;
}
else
{
if (Platform.AppContext.CheckSelfPermission(Android.Manifest.Permission.ManageExternalStorage) == Permission.Granted)
return PermissionStatus.Granted;
var intent = new Intent(Android.Provider.Settings.ActionManageAppAllFilesAccessPermission);
intent.AddCategory(Android.Content.Intent.CategoryDefault);
intent.SetData(Android.Net.Uri.Parse($"package:{Platform.AppContext.PackageName}"));
Platform.CurrentActivity.StartActivity(intent);
return await CheckStatusAsync();
}
}
public override void EnsureDeclared()
{
var packageName = Platform.AppContext.PackageName;
var info = Platform.AppContext.PackageManager.GetPackageInfo(packageName, PackageInfoFlags.Permissions);
var declaredPermissions = info?.RequestedPermissions?.ToList() ?? new List<string>();
if (Build.VERSION.SdkInt < BuildVersionCodes.R)
{
if (!declaredPermissions.Contains(Android.Manifest.Permission.ReadExternalStorage))
throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.ReadExternalStorage} in AndroidManifest.xml");
if (!declaredPermissions.Contains(Android.Manifest.Permission.WriteExternalStorage))
throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.WriteExternalStorage} in AndroidManifest.xml");
}
else
{
if (!declaredPermissions.Contains(Android.Manifest.Permission.ManageExternalStorage))
throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.ManageExternalStorage} in AndroidManifest.xml");
}
}
public override bool ShouldShowRationale()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.R)
{
return Platform.CurrentActivity.ShouldShowRequestPermissionRationale(Android.Manifest.Permission.ReadExternalStorage)
|| Platform.CurrentActivity.ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
}
else
{
return false;
}
}
}
This worked for me
public class AllFileSystemPermission : Permissions.BasePermission { public override Task<PermissionStatus> CheckStatusAsync() { if (Build.VERSION.SdkInt < BuildVersionCodes.R) { var readStatus = Permissions.CheckStatusAsync<Permissions.StorageRead>().Result; var writeStatus = Permissions.CheckStatusAsync<Permissions.StorageWrite>().Result; if (readStatus == PermissionStatus.Granted && writeStatus == PermissionStatus.Granted) return Task.FromResult(PermissionStatus.Granted); if (readStatus == PermissionStatus.Denied || writeStatus == PermissionStatus.Denied) return Task.FromResult(PermissionStatus.Denied); return Task.FromResult(PermissionStatus.Unknown); } else { var manageStatus = Platform.AppContext.CheckSelfPermission(Android.Manifest.Permission.ManageExternalStorage); return Task.FromResult(manageStatus == Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied); } } public override async Task<PermissionStatus> RequestAsync() { if (Build.VERSION.SdkInt < BuildVersionCodes.R) { var readStatus = await Permissions.RequestAsync<Permissions.StorageRead>(); var writeStatus = await Permissions.RequestAsync<Permissions.StorageWrite>(); if (readStatus == PermissionStatus.Granted && writeStatus == PermissionStatus.Granted) return PermissionStatus.Granted; if (readStatus == PermissionStatus.Denied || writeStatus == PermissionStatus.Denied) return PermissionStatus.Denied; return PermissionStatus.Unknown; } else { if (Platform.AppContext.CheckSelfPermission(Android.Manifest.Permission.ManageExternalStorage) == Permission.Granted) return PermissionStatus.Granted; var intent = new Intent(Android.Provider.Settings.ActionManageAppAllFilesAccessPermission); intent.AddCategory(Android.Content.Intent.CategoryDefault); intent.SetData(Android.Net.Uri.Parse($"package:{Platform.AppContext.PackageName}")); Platform.CurrentActivity.StartActivity(intent); return await CheckStatusAsync(); } } public override void EnsureDeclared() { var packageName = Platform.AppContext.PackageName; var info = Platform.AppContext.PackageManager.GetPackageInfo(packageName, PackageInfoFlags.Permissions); var declaredPermissions = info?.RequestedPermissions?.ToList() ?? new List<string>(); if (Build.VERSION.SdkInt < BuildVersionCodes.R) { if (!declaredPermissions.Contains(Android.Manifest.Permission.ReadExternalStorage)) throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.ReadExternalStorage} in AndroidManifest.xml"); if (!declaredPermissions.Contains(Android.Manifest.Permission.WriteExternalStorage)) throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.WriteExternalStorage} in AndroidManifest.xml"); } else { if (!declaredPermissions.Contains(Android.Manifest.Permission.ManageExternalStorage)) throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.ManageExternalStorage} in AndroidManifest.xml"); } } public override bool ShouldShowRationale() { if (Build.VERSION.SdkInt < BuildVersionCodes.R) { return Platform.CurrentActivity.ShouldShowRequestPermissionRationale(Android.Manifest.Permission.ReadExternalStorage) || Platform.CurrentActivity.ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage); } else { return false; } } }
Which is the Platform library?
This worked for me
public class AllFileSystemPermission : Permissions.BasePermission { public override Task<PermissionStatus> CheckStatusAsync() { if (Build.VERSION.SdkInt < BuildVersionCodes.R) { var readStatus = Permissions.CheckStatusAsync<Permissions.StorageRead>().Result; var writeStatus = Permissions.CheckStatusAsync<Permissions.StorageWrite>().Result; if (readStatus == PermissionStatus.Granted && writeStatus == PermissionStatus.Granted) return Task.FromResult(PermissionStatus.Granted); if (readStatus == PermissionStatus.Denied || writeStatus == PermissionStatus.Denied) return Task.FromResult(PermissionStatus.Denied); return Task.FromResult(PermissionStatus.Unknown); } else { var manageStatus = Platform.AppContext.CheckSelfPermission(Android.Manifest.Permission.ManageExternalStorage); return Task.FromResult(manageStatus == Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied); } } public override async Task<PermissionStatus> RequestAsync() { if (Build.VERSION.SdkInt < BuildVersionCodes.R) { var readStatus = await Permissions.RequestAsync<Permissions.StorageRead>(); var writeStatus = await Permissions.RequestAsync<Permissions.StorageWrite>(); if (readStatus == PermissionStatus.Granted && writeStatus == PermissionStatus.Granted) return PermissionStatus.Granted; if (readStatus == PermissionStatus.Denied || writeStatus == PermissionStatus.Denied) return PermissionStatus.Denied; return PermissionStatus.Unknown; } else { if (Platform.AppContext.CheckSelfPermission(Android.Manifest.Permission.ManageExternalStorage) == Permission.Granted) return PermissionStatus.Granted; var intent = new Intent(Android.Provider.Settings.ActionManageAppAllFilesAccessPermission); intent.AddCategory(Android.Content.Intent.CategoryDefault); intent.SetData(Android.Net.Uri.Parse($"package:{Platform.AppContext.PackageName}")); Platform.CurrentActivity.StartActivity(intent); return await CheckStatusAsync(); } } public override void EnsureDeclared() { var packageName = Platform.AppContext.PackageName; var info = Platform.AppContext.PackageManager.GetPackageInfo(packageName, PackageInfoFlags.Permissions); var declaredPermissions = info?.RequestedPermissions?.ToList() ?? new List<string>(); if (Build.VERSION.SdkInt < BuildVersionCodes.R) { if (!declaredPermissions.Contains(Android.Manifest.Permission.ReadExternalStorage)) throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.ReadExternalStorage} in AndroidManifest.xml"); if (!declaredPermissions.Contains(Android.Manifest.Permission.WriteExternalStorage)) throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.WriteExternalStorage} in AndroidManifest.xml"); } else { if (!declaredPermissions.Contains(Android.Manifest.Permission.ManageExternalStorage)) throw new PermissionException($"You must declare the permission {Android.Manifest.Permission.ManageExternalStorage} in AndroidManifest.xml"); } } public override bool ShouldShowRationale() { if (Build.VERSION.SdkInt < BuildVersionCodes.R) { return Platform.CurrentActivity.ShouldShowRequestPermissionRationale(Android.Manifest.Permission.ReadExternalStorage) || Platform.CurrentActivity.ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage); } else { return false; } } }
Which is the Platform library?
the one from Xamarin.Essentials
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Android.OS;
using Android.Content.PM;
using Android.App;
using Android.Content;
Does any one found solution for this, I am getting same error?
Does any one found solution for this, I am getting same error?
Yeah, if you look at the previous messages, the solution is more or less:
look at the workarounds, mostly this one (you might not need it, but i needed in my case):
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);
}
Can someone help me solve the storage permission error, create a test app and I need to know what the code would be to request permission in Android 13. My mainactivity is
using Android.App; using Android.Content.PM; using Android.Runtime; using Android.OS;
namespace MvvmPrueba.Droid { [Activity(Label = "MvvmPrueba", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )] public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
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);
}
}
}
Scroll up.. there are examples there. look @ https://github.com/xamarin/Essentials/issues/2041#issuecomment-1790605848
already add the following permissions
<use-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
What would be the permission to write to the storage?
already add the following permissions
<use-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
What would be the permission to write to the storage?
already add the following permissions What would be the permission to write to the storage?
@juanes030 From what I understood, from android 10+ you don't need permissions to access your own files (https://developer.android.com/training/data-storage/shared/media#storage-permission) But you need permissions to read files created by other apps. (https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions(url))
with that said, i would recommend that you use at least these
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:minSdkVersion="33" android:name="android.permission.READ_MEDIA_VIDEO"/>
I need to archivate from xamarin the permission called file ando multimedia content, like this in the attached image
I think this has been fixed by #2073.
Worked for me by updating to Xamarin Essentials 1.7.5
I found an easy solution.
You don't need anything from above:
Fixed. Now the file picker behaves as expected !
No other change.
Why ? Dunno why an older version of xamarin essentials is used by the android project (even when built by appcenter !). I verified and it's 1.8.0 everywhere.
I found an easy solution.
You don't need anything from above:
- Update to Xamarin Essentials 1.8.0 (latest version)
- Add the "Xamarin Essentials" nuget to the xamarin Android project.
Fixed. Now the file picker behaves as expected ! No other change.
Why ? Dunno why an older version of xamarin essentials is used by the android project (even when built by appcenter !). I verified and it's 1.8.0 everywhere.
Xamarin Essentials 1.8 alone definitely doesn't fix the problem. You need one of the workarounds described earlier in this issue. If your app targets SDK 33 or higher, the app needs to request the READ_MEDIA_IMAGES permission.
Description
I have a strange thing when My TARGET ANDROID VERSION is Android 13.0 (API Level 33)
When i use CrossMedia plugin, i want to ask manual permissions to external storage When i do
--> NO displayAlert comes to ask permission and the result is always "Denied"
for other asking like Camera, the displayAlert comes normally and it's ok.
To test i use xamarin essential mediaPicker when i do var photo = await MediaPicker.CapturePhotoAsync();
First i have normal alert with "Allow ... to take pictures an record video" After that i have a PermissionException
.... And NOT storage permission asking alert
WHEN My TARGET ANDROID VERSION is Android 12.1 (API Level 32) ALL WORKS FINE
In my manifest i have
Expected Behavior
Alert with Storage authorization asking
Actual Behavior
PermissionException StorageWrite permission was not granted: Denied
Basic Information
Version with issue: xamarin essential 1.7.3
Last known good version:
IDE: visual studio mac 2022 17.3.3 (build 10)
Platform Target Frameworks:
Affected Devices: emulator pixel 5 on api 33 , real pixel 6 on api 33