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?