jfversluis / FilePicker-Plugin-for-Xamarin-and-Windows

FilePicker Plugin for Xamarin and Windows
MIT License
157 stars 80 forks source link

InvalidOperationException("Only one operation can be active at a time"); #131

Closed ChristianKleineidam closed 5 years ago

ChristianKleineidam commented 5 years ago

Expected Behavior

When the user clicks on the button that starts the file picker button, the file picker should always be started and the user should get the dialog.

Actual Behavior

The file picker throws InvalidOperationException("Only one operation can be active at a time");

Steps to Reproduce the Problem

  1. Activate the file-picker in app#1
  2. Don't select a file but switch to app#2 while the app#1 with the file picker dialog is still running
  3. Let app#2 share a file and receive the file via an intent filter with app#1 and give the file to the Xamarin Forms page that hosts the file picker.
  4. Activate the file-picker again
  5. The file picker throws InvalidOperationException("Only one operation can be active at a time");

Instead of throwing InvalidOperationException("Only one operation can be active at a time"); the file-picker plugin should end the existing operation and create a new active operation.

Specifications

vividos commented 5 years ago

Thanks for the bug report. It might be that you can store the task that is returned by PickFile() and try to cancel it when you re-enter your picking method and trying to call PickFile() again. Did you already try to apply some fix? Alternatively you could try to fix the FilePicker code and send us a PR. I don't know exactly where you could recognize that the file picker is cancelled, maybe in a OnDestroy() handler.

ChristianKleineidam commented 5 years ago

At the moment I simply tell the user to restart the app.

I'm not sure why this error is needed in the first place. Why can't the function do the same thing it would do when it would be called normally?

jfversluis commented 5 years ago

This is something we picked up from the "legacy" code. Not sure why it happens, might be good to investigate but I'm sure there was a good reason at the time :)

vividos commented 5 years ago

I debugged this issue and I can reproduce it. When calling PickFile(), a temporary activity (FilePickerActivity) is started, which creates the actual file picker activity using Intent.CreateChooser(). This activity then gets suspended when changing to another app, e.g. the "Downloads" app. From there the original app could be activated using IntentFilter, e.g. for a specific file type or custom URL schema. Then OnNewIntent is called, and due to the Activity's attribute "LaunchMode = LaunchMode.SingleTask" the initial activity is shown (e.g. MainActivity for Forms apps). In all this, the file picker plugin doesn't get notified that the file picker isn't shown anymore, and the next PickFile() call throws the exception mentioned.

Honestly, I don't know exactly how to fix that. The Interlocked.CompareExchange() call that throws the exception stores a TaskCompletionSource in a static variable that is needed to report back the task result (FileData). I guess this was done to prevent concurrent access to the TaskCompletionSource. Maybe it works to just cancel the old TaskCompletionSource and continue running. What are your thoughts?

Another ugly issue appears when permissions have to be requested. Then the temporary FilePickerActivity is shown, and it uses the default theme. The best would be if we could use the current activity (e.g. using the CurrentActivity plugin) and request the permission using the Permissions activity. Maybe it's time to port this plugin over to Xamarin.Essentials...

vividos commented 5 years ago

Well, I experimented a bit, and added the following code to PickFileAsync() in FilePickerImplementation.android.cs:

        ...
        var id = this.GetRequestId(); // <-- existing code

        var previousTcs = Interlocked.Exchange(ref this.completionSource, null);
        if (previousTcs != null)
        {
            previousTcs.TrySetCanceled();
        }

        var ntcs = new TaskCompletionSource<FileData>(id); // <-- existing code
        ...

With this, the second call to PickFile() correctly starts file picking. Unfortunately the previous PickFile() call is still pending, and it gets a TaskCanceledException. So the user would have to catch and ignore that exception, which is not optimal.

@ChristianKleineidam Can you compile the FilePicker from source, add the lines and try out if that works with your app? Thanks!

ChristianKleineidam commented 5 years ago

Currently, the packages that I'm using in my App come directly from nuget. I'm not exactly sure how to go about using the self-compiled FilePicker instead.

When compiling the FilePicker I get:

1>------ Build started: Project: Plugin.FilePicker.Abstractions, Configuration: Release Any CPU ------
1>FileData.cs(94,28,94,45): warning CS0618: 'FileData.ReadFully(Stream)' is obsolete: 'ReadFully() is an implementation detail, please use DataArray or GetStream()'
1>Plugin.FilePicker.Abstractions -> C:\Users\Christian\Source\Repos\FilePicker-Plugin-for-Xamarin-and-Windows-1\src\Plugin.FilePicker\Plugin.FilePicker.Abstractions\bin\Release\netstandard1.0\Plugin.FilePicker.Abstractions.dll
1>Done building project "Plugin.FilePicker.Abstractions.csproj".
2>------ Build started: Project: Plugin.FilePicker, Configuration: Release Any CPU ------
2>Plugin.FilePicker -> C:\Users\Christian\Source\Repos\FilePicker-Plugin-for-Xamarin-and-Windows-1\src\Plugin.FilePicker\Plugin.FilePicker\bin\Release\netstandard1.0\Plugin.FilePicker.dll
========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Where do I have to move the resulting files?

vividos commented 5 years ago

It's easy. Remove the NuGet package and add the ner standard Plugin.FilePicker.dll to your Forms project and the MonoAndroid90 version to your Android project. About the warning message... Are you still using the develop branch? Latest development is on matter.

vividos commented 5 years ago

@ChristianKleineidam Can you test your app with version 2.1.17-beta if it works for you now? I tested with my app and the exception is gone; instead the first PickFile() call is returning null, which I handle with showing nothing to the user. Thanks!

ChristianKleineidam commented 5 years ago

@vividos The core functionality is broken on Android. When I try to select a file I get the error:

[0:] Java.Lang.SecurityException: Permission Denial: opening provider com.estrongs.android.pop.app.FileContentProvider from ProcessRecord{9d18894 32502:com.ra_micro.dekrypter/u0a106} (pid=32502, uid=10106) that is not exported from uid 10159
  at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualObjectMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00089] in <fdf05f528e174febb3e55b587dbab368>:0 
  at Java.Interop.JniPeerMembers+JniInstanceMethods.InvokeNonvirtualObjectMethod (System.String encodedMember, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x0001f] in <fdf05f528e174febb3e55b587dbab368>:0 
  at Android.Content.ContentResolver.Query (Android.Net.Uri uri, System.String[] projection, System.String selection, System.String[] selectionArgs, System.String sortOrder) [0x000aa] in <2960acf2eeb24d88b5230e1e8afbdc2e>:0 
  at Plugin.FilePicker.IOUtil.GetDataColumn (Android.Content.Context context, Android.Net.Uri uri, System.String selection, System.String[] selectionArgs) [0x00015] in D:\a\1\s\src\Plugin.FilePicker\Android\IOUtil.android.cs:151 
  at Plugin.FilePicker.IOUtil.GetPath (Android.Content.Context context, Android.Net.Uri uri) [0x001f9] in D:\a\1\s\src\Plugin.FilePicker\Android\IOUtil.android.cs:115 
  at Plugin.FilePicker.FilePickerActivity.OnActivityResult (System.Int32 requestCode, Android.App.Result resultCode, Android.Content.Intent data) [0x0003c] in D:\a\1\s\src\Plugin.FilePicker\Android\FilePickerActivity.android.cs:147 
  --- End of managed Java.Lang.SecurityException stack trace ---
java.lang.SecurityException: Permission Denial: opening provider com.estrongs.android.pop.app.FileContentProvider from ProcessRecord{9d18894 32502:com.ra_micro.dekrypter/u0a106} (pid=32502, uid=10106) that is not exported from uid 10159
    at android.os.Parcel.readException(Parcel.java:1684)
    at android.os.Parcel.readException(Parcel.java:1637)
    at android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:4208)
    at android.app.ActivityThread.acquireProvider(ActivityThread.java:5509)
    at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:2314)
    at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:1520)
    at android.content.ContentResolver.query(ContentResolver.java:518)
    at android.content.ContentResolver.query(ContentResolver.java:475)
    at md54fe4aef201482be7d95d72c0c9bf0b33.FilePickerActivity.n_onActivityResult(Native Method)
    at md54fe4aef201482be7d95d72c0c9bf0b33.FilePickerActivity.onActivityResult(FilePickerActivity.java:47)
    at android.app.Activity.dispatchActivityResult(Activity.java:6981)
    at android.app.ActivityThread.deliverResults(ActivityThread.java:4105)
    at android.app.ActivityThread.handleSendResult(ActivityThread.java:4152)
    at android.app.ActivityThread.-wrap20(ActivityThread.java)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1537)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:179)
    at android.app.ActivityThread.main(ActivityThread.java:6152)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
vividos commented 5 years ago

com.estrongs.android.pop.app seems to be the app "ES File Explorer/Manager PRO", whose content provider throws an exception. My guess is that this is a different issue. Could you test your original use case with some other provider, e.g. Google Drive, OneDrive or just files from the Download folder? Thanks! You can of course open a new issue for the "ES File Explorer/Manager PRO" app.

ChristianKleineidam commented 5 years ago

Okay, I tested again and it works with Google Drive. I think the update is good to go.

I remembered that it worked in a previous version with ES File Explorer and another File Manager but it seems like both apps now stopped working even with the old version of FilePicker, so the update of the FilePicker doesn't seem to be an issue.

vividos commented 5 years ago

Thanks for confirming!

jfversluis commented 5 years ago

@vividos we want to go live with this one?