problem opening camera with WebView and input type file in Xamarin Forms only Android

Esteban Orellana 36 Reputation points
2020-12-15T02:01:15.3+00:00

Good day to all.

I have researched a lot about how to open the camera through a file type input through a WebView in the Android version (it is a Xamarin.Forms project)

I have tried two ways, the first one, using the PermissionsPlugin library and that of a CustomWebView with their respective Renderer and I have not been able to make it work

Este es mi CustomWebViewRenderer:

public class CustomWebViewRenderer : WebViewRenderer
    {
        //Activity mContext;

        public CustomWebViewRenderer(Android.Content.Context context) : base(context)
        {
            //this.mContext = context as Activity;
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
        {
            base.OnElementChanged(e);
            Control.Settings.JavaScriptEnabled = true;
            Control.Settings.DomStorageEnabled = true;
            Control.Settings.SetPluginState(WebSettings.PluginState.On);

            Control.ClearCache(true);

            CustomWebClient cwc = new CustomWebClient(MainActivity.Instance);
            Control.SetWebChromeClient(cwc);
        }

        public class CustomWebClient : WebChromeClient
        {
            Activity mActivity = null;

            public CustomWebClient(Activity activity)
            {
                mActivity = activity;
            }

            [TargetApi(Value = 21)]
            public override void OnPermissionRequest(PermissionRequest request)
            {
                if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
                {
                    mActivity.RunOnUiThread(() =>
                    {
                        request.Grant(request.GetResources());
                    });
                }
            }
        }
    }

This is my MainActivity.cs:

public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        public static MainActivity Instance { get; private set; }

        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Instance = this;

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            CrossCurrentActivity.Current.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

            CachedImageRenderer.Init(false);
            AnimationViewRenderer.Init();
            LoadApplication(new App());
        }

        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);
            Plugin.Permissions.PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

and these are the permissions assigned in the AndroidManifest.xml:

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

<uses-feature android:name="android.hardware.location" android:required="false" />
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location.network" android:required="false" />

Any information on how to achieve this would be greatly appreciated. I wait for a hand.

Best regards

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,301 questions
{count} votes

Accepted answer
  1. JarvanZhang 23,951 Reputation points
    2020-12-17T06:11:32.103+00:00

    Hello,​

    Welcome to our Microsoft Q&A platform!

    To choose a file in WebView, try to override the OnShowFileChooser method in the custom WebChromeClient class. Check the following code:

       public class CustomWebChromeClient : WebChromeClient  
       {  
           Activity mActivity = null;  
           public CustomWebChromeClient(Activity activity)  
           {  
               mActivity = activity;  
           }  
           public override bool OnShowFileChooser(Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)  
           {  
               MainActivity.mUploadCallbackAboveL = filePathCallback;  
         
               //TakePhoto();  
               File imageStorageDir = new File(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures), "MyAppFolder");  
         
               if (!imageStorageDir.Exists())  
               {  
                   imageStorageDir.Mkdirs();  
               }  
               // Create camera captured image file path and name, add ticks to make it unique   
               var file = new File(imageStorageDir + File.Separator + "IMG_" + DateTime.Now.Ticks + ".jpg");  
         
               MainActivity.imageUri = Android.Net.Uri.FromFile(file);  
         
               //Create camera capture image intent and add it to the chooser  
               var captureIntent = new Intent(MediaStore.ActionImageCapture);  
               captureIntent.PutExtra(MediaStore.ExtraOutput, MainActivity.imageUri);  
         
               var i = new Intent(Intent.ActionGetContent);  
               i.AddCategory(Intent.CategoryOpenable);  
               i.SetType("image/*");  
         
               var chooserIntent = Intent.CreateChooser(i, "Choose image");  
               chooserIntent.PutExtra(Intent.ExtraInitialIntents, new Intent[] { captureIntent });  
         
               MainActivity.Instance.StartActivityForResult(chooserIntent, MainActivity.PHOTO_REQUEST);  
               return true;  
           }  
       }  
    

    MainActivity class

       public class MainActivity : AppCompatActivity  
       {  
           public static IValueCallback mUploadCallbackAboveL;  
           public static Android.Net.Uri imageUri;  
           public static MainActivity Instance;  
           public static int PHOTO_REQUEST = 10023;  
         
           public static IValueCallback mUploadMessage;  
           public static int FILECHOOSER_RESULTCODE = 1;  
           protected override void OnCreate(Bundle savedInstanceState)  
           {  
               base.OnCreate(savedInstanceState);  
               Xamarin.Essentials.Platform.Init(this, savedInstanceState);  
               // Set our view from the "main" layout resource  
               Instance = this;  
           }  
           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);  
           }  
         
           protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)  
           {  
               if (requestCode == FILECHOOSER_RESULTCODE)  
               {  
                   if (null == mUploadMessage) return;  
                   Android.Net.Uri result = intent == null || resultCode != Result.Ok ? null : intent.Data;  
                   mUploadMessage.OnReceiveValue(result);  
                   mUploadMessage = null;  
               }  
               else if (requestCode == PHOTO_REQUEST)  
               {  
                   Android.Net.Uri result = intent == null || resultCode != Result.Ok ? null : intent.Data;  
         
                   if (mUploadCallbackAboveL != null)  
                   {  
                       onActivityResultAboveL(requestCode, resultCode, intent);  
                   }  
                   else if (mUploadMessage != null)  
                   {  
                       mUploadMessage.OnReceiveValue(result);  
                       mUploadMessage = null;  
                   }  
               }  
           }  
           private void onActivityResultAboveL(int requestCode, Result resultCode, Intent data)  
           {  
               if (requestCode != PHOTO_REQUEST || mUploadCallbackAboveL == null)  
               {  
                   return;  
               }  
               Android.Net.Uri[] results = null;  
               if (resultCode == Result.Ok)  
               {  
                   results = new Android.Net.Uri[] { imageUri };  
                   results[0] = MainActivity.imageUri;  
               }  
               mUploadCallbackAboveL.OnReceiveValue(results);  
               mUploadCallbackAboveL = null;  
         
           }  
       }  
    

    Similar issue you could refer to: https://stackoverflow.com/questions/62656020/xamarin-android-webview-dont-open-camera

    Best Regards,

    Jarvan Zhang


    If the response is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

4 additional answers

Sort by: Most helpful
  1. AshySlashy87 6 Reputation points
    2021-02-27T23:08:32.373+00:00

    For the Camera it works great!

    But not for the FileChooser

    The requestCode on the OnActivityResult Method always return the Code from the PHOTO_REQUEST --> 10023

     protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)
            {
                System.Console.WriteLine(" # onActivityResult # requestCode=" + requestCode + " # resultCode=" + resultCode); //ALWAYS RETURN 10023
    

    any hints?

    1 person found this answer helpful.

  2. Nicholas Poh 6 Reputation points
    2021-08-27T09:17:56.727+00:00

    Here's a cleaned up version of OnActivityResult(). We need to call
    filePathCallback.onReceiveValue(null)
    to cancel the request in case the user tapped on "Back". This will allow a new request to be initiated.
    onActivityResultAboveL()
    can be removed.

        protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)
        {
            if (requestCode == PHOTO_REQUEST)
            {
                if (resultCode == Result.Ok)
                {
                    Android.Net.Uri result = intent.Data ?? ImageUri;
                    if (UploadCallbackAboveL != null)
                    {
                        UploadCallbackAboveL.OnReceiveValue(new Android.Net.Uri[] { result });
                    }
                }
                else
                {
                    UploadCallbackAboveL.OnReceiveValue(null);
                }
                UploadCallbackAboveL = null;
            }
            else
            {
                base.OnActivityResult(requestCode, resultCode, intent);
            }
        }
    

    My only confusion is why can't the uri be placed in the Data property of the
    captureIntent
    and is passed through a global variable
    ImageUri
    . When I tried setting the Data property, the chooser is not displayed and the default Files option is selected. Is there a better approach?

    1 person found this answer helpful.
    0 comments No comments

  3. AshySlashy87 6 Reputation points
    2021-03-01T22:31:23.02+00:00

    Don't know if this is the best way to handle this... but it works...

    my changes:

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent intent)
            {
                System.Console.WriteLine(" # onActivityResult # requestCode=" + requestCode + " # Intent=" + intent.DataString);
    
                if (intent.DataString != null)
                {
                    if (null == mUploadCallbackAboveL) return;
                    Android.Net.Uri result = intent == null || resultCode != Result.Ok ? null : intent.Data;
    
                    mUploadCallbackAboveL.OnReceiveValue(new Android.Net.Uri[] { result });
                    mUploadCallbackAboveL = null;
    
                }
                else if (requestCode == PHOTO_REQUEST)
                {
                    Android.Net.Uri result = intent == null || resultCode != Result.Ok ? null : intent.Data;
                    if (mUploadCallbackAboveL != null)
                    {
                        onActivityResultAboveL(requestCode, resultCode, intent);
                    }
                    else if (mUploadCallbackAboveL != null)
                    {
                        mUploadCallbackAboveL.OnReceiveValue(result);
                        mUploadCallbackAboveL = null;
                    }
                }
            }
    
    0 comments No comments

  4. Chirag Pokiya 96 Reputation points
    2021-12-27T13:47:36.507+00:00
    I have a simple Xamarin Page with a WebView of above code that calls a WebRTC page but camera not working.
    
    What's wrong? I am at a loss and could need some help/guidance.
    
    Thank you and Please look into my attached code.
    
    
    [WebViewDemo][1]
    
    
      [1]: https://github.com/projectrbkei/WebViewDemo
    
    0 comments No comments