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

[Bug][Android] Picking photo from Photos app leads to app crash unless we explicitly request StorageRead permissions on Android 9 #2099

@MitchBomcanhao

Description

@MitchBomcanhao

Description

After updating our solution to the newest Android 13 supporting packages from Essentials and Forms, and adapting the code to the new expectations regarding permissions, I've found that I could make the photo picker crash with a UnauthorizedAccessException whenever I was on Android 9 and I picked a photo in a certain way.
I cannot reproduce this issue on devices running Android 10 and higher, which I assume is related to changes in permissions that also resulted in previous issues such as the following, which were identified on android 10+:

It is possible that the same issue already existed in older versions of essentials but as there were different expectations regarding permissions, ie we always requested Permissions.StorageRead when picking photos. Now the expectation is that you might not have to explicitly request storage permissions and it seems to still work, except on some older android versions.

Xamarin essentials actually checks for certain permissions before running some of its picking/camera tasks (and some of those checks are even api level dependent), so it is sort of implied that if essentials does not check for a permission, it is likely unnecessary? is there something in the documentation that says we have to request Permissions.StorageRead when picking photos, even though essentials doesn't check for it and it doesn't seem to be necessary for android 10+? is there an exception somewhere that says we have to still request it for android 9 and under?

Steps to Reproduce

  1. take at least one photo with the device or emulator.
  2. run the test application and trigger the media picker task
  3. when it opens the media picker interface, tap the hamburger menu on the top left and switch to the "Photos" app context at the bottom.
  4. Now select the relevant folder and pick a photo file.
  5. the code will attempt to access the file's contents;
  6. Observe the unexpected behaviour.
    1. the app will crash with a System.UnauthorizedAccessException

Expected Behavior

File is accessible after picking.

Actual Behavior

App will crash with an UnauthorizedAccessException when we attempt to access it.

**System.UnauthorizedAccessException:** 'Access to the path "/storage/emulated/0/DCIM/Camera/IMG_20230905_122553.jpg" is denied.'

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.UnauthorizedAccessException: Access to the path "/storage/emulated/0/DCIM/Camera/IMG_20230905_122612.jpg" is denied.
[mono-rt]   at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x001aa] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/corlib/System.IO/FileStream.cs:239 
[mono-rt]   at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/corlib/System.IO/FileStream.cs:91 
[mono-rt]   at (wrapper remoting-invoke-with-check) System.IO.FileStream..ctor(string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare)
[mono-rt]   at System.IO.File.OpenRead (System.String path) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/external/corefx/src/System.IO.FileSystem/src/System/IO/File.cs:266 
[mono-rt]   at Xamarin.Essentials.FileBase.PlatformOpenReadAsync () [0x00000] in D:\a\_work\1\s\Xamarin.Essentials\FileSystem\FileSystem.android.cs:384 
[mono-rt]   at Xamarin.Essentials.FileBase.OpenReadAsync () [0x00000] in D:\a\_work\1\s\Xamarin.Essentials\FileSystem\FileSystem.shared.cs:172 
[mono-rt]   at AndroidPickerBug.MainPage.Button_Clicked (System.Object sender, System.EventArgs e) [0x000b2] in C:\Users\redacted\source\repos\AndroidPickerBug\AndroidPickerBug\MainPage.xaml.cs:25 
[mono-rt]   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) [0x00000] in /Users/builder/jenkins/workspace/archive-mono/2020-02/android/release/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1021 
[mono-rt]   at Android.App.SyncContext+<>c__DisplayClass2_0.<Post>b__0 () [0x00000] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:36 
[mono-rt]   at Java.Lang.Thread+RunnableImplementor.Run () [0x00008] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:36 
[mono-rt]   at Java.Lang.IRunnableInvoker.n_Run (System.IntPtr jnienv, System.IntPtr native__this) [0x00008] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/monoandroid10/android-33/mcw/Java.Lang.IRunnable.cs:84 
[mono-rt]   at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V (_JniMarshal_PP_V callback, System.IntPtr jnienv, System.IntPtr klazz) [0x00005] in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:22 
[mono-rt]   at (wrapper native-to-managed) Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(intptr,intptr)

Basic Information

  • Version with issue: 1.8.0
  • Last known good version: not sure if applicable as permissions changed a great deal since target api 33 has been mandatory.
  • IDE: VS2022 17.7.1
  • Platform Target Frameworks:
    • Android: 33

Reproduction Link

Android's AssemblyInfo.cs file declares:

[assembly: UsesPermission(MaxSdkVersion = 32, Name = Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(MaxSdkVersion = 32, Name = Android.Manifest.Permission.ReadExternalStorage)]

I just have a plain xaml page with a button that runs this on its clicked event:

        private async void Button_Clicked(object sender, EventArgs e)
        {
            var result = await Xamarin.Essentials.MediaPicker.PickPhotoAsync();

            if (result is FileResult fr) 
            {
                using (var fileStream = await fr.OpenReadAsync())
                {
                    // this will blow up if file permission is not actually granted
                }
            }            
        }

The above code will fail on fr.OpenReadAsync() when you're on android 9 and you haven't got StorageRead permission. It'll work fine on Android 10+.

Workaround

Call await Xamarin.Essentials.Permissions.RequestAsync<Permissions.StorageRead>(); before running this code, only if your device is running an android version lower than 10.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions