Mono for Android, WebView file input device not working

I have a webpage that uses to upload files. The user selects the file using <input type="file" /> and presses the submit button, everything works fine. Now I need to create an Android application (in C # with mono for android) that contains simple web browsing and should work as a web version.

But I came across a problem - when I click the Choose file button, then the file dialog does not open.

I ran into this problem for several days, but I did not find a solution. There seems to be a workaround on the Java platform , but it does not work in C #.

Does anyone have any ideas how to make it work?

+4
source share
2 answers

I have an idea how to make it work. Some of them are the standard of the swamp "how to connect the virtual method", and some of this is pure carefree evil.

First of all, we need an โ€œintermediaryโ€. Since WebChromeClient does not declare the openFileChooser() method, we need to declare a version that does this with the name OpenFileWebChromeClient . It declares a virtual OpenFileChooser method and provides a binding for it so that it can be overridden:

 using System; using Android.App; using Android.Content; using Android.Runtime; using Android.OS; using Android.Webkit; namespace Scratch.FileUpload { [Register ("android/webkit/WebChromeClient", DoNotGenerateAcw=true)] class OpenFileWebChromeClient : WebChromeClient { static IntPtr id_openFileChooser; [Register ("openFileChooser", "(Landroid/webkit/ValueCallback;)V", "GetOpenFileChooserHandler")] public virtual void OpenFileChooser (IValueCallback uploadMsg) { if (id_openFileChooser == IntPtr.Zero) id_openFileChooser = JNIEnv.GetMethodID (ThresholdClass, "openFileChooser", "(Landroid/webkit/ValueCallback;)V"); if (GetType () == ThresholdType) JNIEnv.CallVoidMethod (Handle, id_openFileChooser, new JValue (JNIEnv.ToJniHandle (uploadMsg))); else JNIEnv.CallNonvirtualVoidMethod (Handle, ThresholdClass, id_openFileChooser, new JValue (JNIEnv.ToJniHandle (uploadMsg))); } #pragma warning disable 0169 static Delegate cb_openFileChooser; static Delegate GetOpenFileChooserHandler () { if (cb_openFileChooser == null) cb_openFileChooser = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr>) n_OpenFileChooser); return cb_openFileChooser; } static void n_OpenFileChooser (IntPtr jnienv, IntPtr native__this, IntPtr native_uploadMsg) { OpenFileWebChromeClient __this = Java.Lang.Object.GetObject<OpenFileWebChromeClient> (native__this, JniHandleOwnership.DoNotTransfer); var uploadMsg = Java.Lang.Object.GetObject<IValueCallback> (native_uploadMsg, JniHandleOwnership.DoNotTransfer); __this.OpenFileChooser (uploadMsg); } #pragma warning restore 0169 } } 

Further, since there are no anonymous inner classes in C #, we need an explicit class called MyOpenFileWebChromeClient here:

 namespace Scratch.FileUpload { class MyOpenFileWebChromeClient : OpenFileWebChromeClient { Action<IValueCallback> cb; public MyOpenFileWebChromeClient(Action<IValueCallback> cb) { this.cb = cb; } public override void OpenFileChooser (IValueCallback uploadMsg) { cb (uploadMsg); } } 

The activity port ~ is identical to the blog post that you linked to, except that it uses MyOpenFileWebChromeClient instead of <anonymous inner class. I also updated some logic to display the URI that OnActivityResult() gets:

 namespace Scratch.FileUpload { [Activity (Label = "Scratch.FileUpload", MainLauncher = true)] public class Activity1 : Activity { private WebView wv; private IValueCallback mUploadMessage; const int FilechooserResultcode = 1; protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); wv = new WebView (this); wv.SetWebViewClient(new WebViewClient()); wv.SetWebChromeClient(new MyOpenFileWebChromeClient(uploadMsg => { mUploadMessage = uploadMsg; var intent = new Intent (Intent.ActionGetContent); intent.AddCategory(Intent.CategoryOpenable); intent.SetType("image/*"); StartActivityForResult(Intent.CreateChooser(intent, "File Chooser"), FilechooserResultcode); })); SetHtml(null); SetContentView(wv); } void SetHtml(string filename) { string html = @"<html> <body> <h1>Hello, world!</h1> <p>Input Box:</p> <input type=""file"" /> <p>URI: " + filename + @" </body> </html>"; wv.LoadData(html, "text/html", "utf-8"); } protected override void OnActivityResult (int requestCode, Result resultCode, Intent data) { base.OnActivityResult (requestCode, resultCode, data); if (requestCode == FilechooserResultcode) { if (mUploadMessage == null) return; var result = data == null || resultCode != Result.Ok ? null : data.Data; SetHtml(result.ToString()); mUploadMessage.OnReceiveValue(result); mUploadMessage = null; } } } } 

Unfortunately, the time has come for an act of pure unconditional evil. The problem with the above declaration for MyOpenFileWebChromeClient is that it will not work for the same reason that the M0S blog could not use @Override in the anonymous declaration of the inner class: android.jar that you create for your application does not declare the openFileChooser() method openFileChooser() .

The build process will generate Android Callable Wrappers , which must contain valid Java code. The problem is that the generated code uses @Override for overridden and interface methods, resulting in an Android Callable Wrapper for MyOpenFileWebChromeClient :

 package scratch.fileupload; public class MyOpenFileWebChromeClient extends android.webkit.WebChromeClient { static final String __md_methods; static { __md_methods = "n_openFileChooser:(Landroid/webkit/ValueCallback;)V:GetOpenFileChooserHandler\n" + ""; mono.android.Runtime.register ("Scratch.FileUpload.MyOpenFileWebChromeClient, Scratch.FileUpload, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", MyOpenFileWebChromeClient.class, __md_methods); } @Override public void openFileChooser (android.webkit.ValueCallback p0) { n_openFileChooser (p0); } private native void n_openFileChooser (android.webkit.ValueCallback p0); java.util.ArrayList refList; public void monodroidAddReference (java.lang.Object obj) { if (refList == null) refList = new java.util.ArrayList (); refList.add (obj); } public void monodroidClearReferences () { if (refList != null) refList.clear (); } } 

Obviously, @Override on MyOpenFileWebChromeClient.openFileChooser() generates a compiler error, so how do we do this? By providing your own @Override annotation!

 package scratch.fileupload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } 

Put this in a file called Override.java , add it to the project and set its Build action to AndroidJavaSource .

The resulting project works because we provide the custom @Override annotation in the same package as the MyOpenFileWebChromeClient type. (Therefore, this requires that you know what the name of the generated package is, and that you provide a separate @Override annotation for each package you do this for.) Types in one package take precedence over imported names, even names coming in from java.lang , so our custom @Override annotation @Override not only compiled, it is used by the wrapper MyOpenFileWebChromeClient android callable, it is preferable to the java.lang.Override annotation.

I said that it was a pure carefree evil, right?

+2
source

step1 = Uploading files will work, we must provide read / write permission in the android manifest. in Core Activity.cs

 step2= private Action<int, Result, Intent> resultCallbackvalue; public void StartActivity(Intent intent, int requestCode, Action<int, Result, Intent> resultCallback) { this.resultCallbackvalue = resultCallback; StartActivityForResult(intent, requestCode); } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (this.resultCallbackvalue != null) { this.resultCallbackvalue(requestCode, resultCode, data); this.resultCallbackvalue = null; } } step3= add ExtendedChromeClient,cs Inheriting from : WebChromeClient private static int filechooser = 1; private IValueCallback message; private MainActivity activity = null; public ExtendedChromeClient(MainActivity context) { this.activity = context; } public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams) { this.message = filePathCallback; Intent chooserIntent = fileChooserParams.CreateIntent(); chooserIntent.AddCategory(Intent.CategoryOpenable); this.activity.StartActivity(Intent.CreateChooser(chooserIntent, "File Chooser"), filechooser, this.OnActivityResult); return true; } private void OnActivityResult(int requestCode, Result resultCode, Intent data) { if (data != null) { if (requestCode == filechooser) { if (null == this.message) { return; } this.message.OnReceiveValue(WebChromeClient.FileChooserParams.ParseResult((int)resultCode, data)); this.message = null; } } } 
0
source

Source: https://habr.com/ru/post/1397586/


All Articles