0

I have some code for picking a file on Android. I picked it from the web somewhere. Actually, the code works in one scenario, but in my app, it doesn't work. Android thinks that the user has cancelled the file pick when they actually haven't.

Here's the code:

private Task<FileData> PickAndOpenFile(bool isSave)
{
    var id = GetRequestId();

    var ntcs = new TaskCompletionSource<FileData>(id);

    if (Interlocked.CompareExchange(ref _CompletionSource, ntcs, null) != null)
    {
        throw new InvalidOperationException("Only one operation can be active at a time");
    }

    try
    {
        var pickerIntent = new Intent(_Context, typeof(FilePickerActivity));
        pickerIntent.SetFlags(ActivityFlags.NewTask);

        _Context.StartActivity(pickerIntent);

        EventHandler<FilePickerEventArgs> handler = null;
        EventHandler<EventArgs> cancelledHandler = null;

        handler = (s, e) =>
        {
            var tcs = Interlocked.Exchange(ref _CompletionSource, null);

            FilePickerActivity.FilePicked -= handler;

            Stream fileStream = isSave ? File.OpenRead(e.FilePath) : File.OpenWrite(e.FilePath);

            tcs?.SetResult(new FileData { FileName = e.FileName, FileStream = fileStream });
        };

        cancelledHandler = (s, e) =>
        {
            var tcs = Interlocked.Exchange(ref _CompletionSource, null);

            FilePickerActivity.FilePickCancelled -= cancelledHandler;

            tcs?.SetResult(null);
        };

        FilePickerActivity.FilePickCancelled += cancelledHandler;
        FilePickerActivity.FilePicked += handler;
    }
    catch (Exception exAct)
    {
        Debug.Write(exAct);
    }

    return _CompletionSource.Task;
}

To see this running correctly, please clone this repo and run the android sample. The choose file example is on the second tab. https://github.com/MelbourneDeveloper/Adapt.Presentation.git

But, in my app, I have exactly the same code used like this:

            var filePicker = App.PlatformSpecificStartupArguments.PresentationFactory.CreateFilePicker();
            using (var fileData = await filePicker.PickAndOpenFileForReading())
            {
                if (fileData == null)
                {
                    return;
                }

                using (var readFileStream = fileData.FileStream)
                {
                    var fileExtension = Path.GetExtension(fileData.FileName);
                    var relativeUri = Guid.NewGuid() + fileExtension;
                    await OnPictureTakenAsync(this, new PictureTakenEventArgs(readFileStream, relativeUri, fileData.FileName));
                }
            }

But, when I pick the file (selecting an image from existing images), the code enters the cancelledHandler instead. This is clearly a permissions problem because I get this error message in the device log:

08-02 15:15:28.697 LGE Nexus 5X Error 18911 DatabaseUtils java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media from pid=18684, uid=10221 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission() at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:608) at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:483) at android.content.ContentProvider$Transport.query(ContentProvider.java:212) at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112) at android.os.Binder.execTransact(Binder.java:565)

So, I turned on this permission in my Android Manifest at the Visual Studio level, but the problem still persists. The version of Android that I am targeting where this is failing is 6.0. You can see that in the repo mentioned above the target version is set to "Use compile using SDK version". And I haven't even added READ_EXTERNAL_STORAGE to that sample app.

Christian Findlay
  • 6,770
  • 5
  • 51
  • 103

1 Answers1

1

I turned on this permission in my Android Manifest at the Visual Studio level, but the problem still persists.

Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. You should requesting permissions at runtime.

System permissions are divided into two categories, normal and dangerous:

  • Normal permissions do not directly risk the user's privacy. If your app lists a normal permission in its manifest, the system grants the permission automatically.
  • Dangerous permissions can give the app access to the user's confidential data. If your app lists a normal permission in its manifest, the system grants the permission automatically. If you list a dangerous permission, the user has to explicitly give approval to your app.

java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider ... requires android.permission.READ_EXTERNAL_STORAGE

READ_EXTERNAL_STORAGE is divided in to Dangerous permissions, so you should checking this permissions manually for API level 23 and above.

Here is an example about how to request permissions at runtime.

York Shen
  • 9,014
  • 1
  • 16
  • 40
  • Thanks. Your post led my to my final answer. I have been using code from James Montemontagno for this, but that code was not premissions requesting ready, so I have edited that code to fix the problem. I will write another post and lay out the full solution. – Christian Findlay Aug 04 '17 at 05:22