Search code examples
androidxamarin.androidcameraandroid-webviewgoogle-chrome-webview

Xamarin Android Webview: getUserMedia fails to open camera


I'm trying to open the camera and allow the user to capture an image frame from within a Xamarin.Android's webview.

I've pointed the webview to this URL which hosts a few examples: https://googlechrome.github.io/samples/image-capture/index.html. The samples work perfectly when opening the URL in the device's Chrome browser but fails when opened within the app's webview. Here is my WebChromeClient implementation:

internal class ChromeWebviewClient : WebChromeClient
{
    public override void OnPermissionRequest(PermissionRequest request)
    {
            request.Grant(request.GetResources());
    }
}

I've got camera permissions declared in my manifest:

<uses-permission android:name="android.permission.CAMERA" />

Navigating to the sample page and tapping on "Get User Media" does not seem to do anything and I see the following in the application log that seems relevant but does not make sense to me:

[CameraManagerGlobal] Connecting to camera service
[VendorTagDescriptor] addVendorDescriptor: vendor tag id 3854507339 added
[CameraManagerGlobal] Camera 0 facing CAMERA_FACING_BACK state now CAMERA_STATE_CLOSED for client com.masterappstudio.qrcodereader API Level 1
[CameraManagerGlobal] Camera 1 facing CAMERA_FACING_FRONT state now CAMERA_STATE_CLOSED for client com.sec.android.app.sbrowser API Level 2
[CameraManagerGlobal] Camera 2 facing CAMERA_FACING_FRONT state now CAMERA_STATE_CLOSED for client android.system API Level 2
[CameraManagerGlobal] Camera 90 facing CAMERA_FACING_FRONT state now CAMERA_STATE_CLOSED for client com.samsung.android.server.iris API Level 2

I've also placed a breakpoint at OnPermissionRequest but it does not appear to trigger.

I suspect this has something to do with runtime camera permissions introduced in Android 6+ (I'm running Android 9). I've gone ahead and set the camera permission to Allowed within the phone settings but does not seem to do anything either.

I've exhausted Google search on this topic (most of the hits are around iconic and phonegap framework) and noticed a few questions here for native Android that echo this problem with 0 answers.


Solution

  • First, adding these permission in AndroidManifeast.xml

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    

    If deploying the application on Android 6.0 or above, it requires to request the permission at runtime.

     private void checkpermission()
        {
            if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) == (int)Permission.Granted)
            {
                // We have permission, go ahead and use the camera.              
            }
            else
            {
                // Camera permission is not granted. If necessary display rationale & request.
                ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.Camera }, 1);               
            }
        }
    

    To load a URL,subclass Android.Webkit.WebViewClient and override the ShouldOverriderUrlLoading method.

     public class HelloWebViewClient : WebViewClient
    {
        // For API level 24 and later
        public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
        {
            view.LoadUrl(request.Url.ToString());
            return false;
        }
    }
    

    Then creating custom WebChromeClient to override OnPermissionRequest method to grant the request.

     public class MyWebChromeClient : WebChromeClient
    {
        Activity mActivity = null;
        public MyWebChromeClient(Activity activity)
        {
            mActivity = activity;
        }
        public override void OnPermissionRequest(PermissionRequest request)
        {
            mActivity.RunOnUiThread(() => {
                request.Grant(request.GetResources());
            });
        }
    }
    
     public class MainActivity : AppCompatActivity
    {
        private WebView webview1;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.activity_main);
            webview1 = FindViewById<WebView>(Resource.Id.webView1);
            webview1.Settings.JavaScriptEnabled = true;
            webview1.Settings.AllowFileAccessFromFileURLs = true;
            webview1.Settings.AllowUniversalAccessFromFileURLs = true;
            webview1.SetWebViewClient(new HelloWebViewClient());
            webview1.SetWebChromeClient(new MyWebChromeClient(this));
            checkpermission();
        }
    
        private void checkpermission()
        {
            if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) == (int)Permission.Granted)
            {
                // We have permission, go ahead and use the camera.
                webview1.LoadUrl("https://googlechrome.github.io/samples/image-capture/index.html");
            }
            else
            {
                // Camera permission is not granted. If necessary display rationale & request.
                ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.Camera }, 1);
                webview1.LoadUrl("https://googlechrome.github.io/samples/image-capture/index.html");
            }
        }
        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }