|  | @@ -2,6 +2,7 @@ package com.reactnativecommunity.webview;
 | 
	
		
			
			| 2 | 2 |  
 | 
	
		
			
			| 3 | 3 |  import android.annotation.SuppressLint;
 | 
	
		
			
			| 4 | 4 |  import android.annotation.TargetApi;
 | 
	
		
			
			|  | 5 | +import android.app.Activity;
 | 
	
		
			
			| 5 | 6 |  import android.app.DownloadManager;
 | 
	
		
			
			| 6 | 7 |  import android.content.Context;
 | 
	
		
			
			| 7 | 8 |  import android.content.Intent;
 | 
	
	
		
			
			|  | @@ -37,6 +38,8 @@ import android.webkit.WebView;
 | 
	
		
			
			| 37 | 38 |  import android.webkit.WebViewClient;
 | 
	
		
			
			| 38 | 39 |  import android.widget.FrameLayout;
 | 
	
		
			
			| 39 | 40 |  
 | 
	
		
			
			|  | 41 | +import com.facebook.react.modules.core.PermissionAwareActivity;
 | 
	
		
			
			|  | 42 | +import com.facebook.react.modules.core.PermissionListener;
 | 
	
		
			
			| 40 | 43 |  import com.facebook.react.views.scroll.ScrollEvent;
 | 
	
		
			
			| 41 | 44 |  import com.facebook.react.views.scroll.ScrollEventType;
 | 
	
		
			
			| 42 | 45 |  import com.facebook.react.views.scroll.OnScrollDispatchHelper;
 | 
	
	
		
			
			|  | @@ -76,7 +79,9 @@ import java.net.MalformedURLException;
 | 
	
		
			
			| 76 | 79 |  import java.net.URL;
 | 
	
		
			
			| 77 | 80 |  import java.net.URLEncoder;
 | 
	
		
			
			| 78 | 81 |  import java.util.ArrayList;
 | 
	
		
			
			|  | 82 | +import java.util.Collections;
 | 
	
		
			
			| 79 | 83 |  import java.util.HashMap;
 | 
	
		
			
			|  | 84 | +import java.util.List;
 | 
	
		
			
			| 80 | 85 |  import java.util.Locale;
 | 
	
		
			
			| 81 | 86 |  import java.util.Map;
 | 
	
		
			
			| 82 | 87 |  
 | 
	
	
		
			
			|  | @@ -868,12 +873,36 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
 | 
	
		
			
			| 868 | 873 |        View.SYSTEM_UI_FLAG_IMMERSIVE |
 | 
	
		
			
			| 869 | 874 |        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 | 
	
		
			
			| 870 | 875 |  
 | 
	
		
			
			|  | 876 | +    protected static final int COMMON_PERMISSION_REQUEST = 3;
 | 
	
		
			
			|  | 877 | +
 | 
	
		
			
			| 871 | 878 |      protected ReactContext mReactContext;
 | 
	
		
			
			| 872 | 879 |      protected View mWebView;
 | 
	
		
			
			| 873 | 880 |  
 | 
	
		
			
			| 874 | 881 |      protected View mVideoView;
 | 
	
		
			
			| 875 | 882 |      protected WebChromeClient.CustomViewCallback mCustomViewCallback;
 | 
	
		
			
			| 876 | 883 |  
 | 
	
		
			
			|  | 884 | +    /*
 | 
	
		
			
			|  | 885 | +     * - Permissions -
 | 
	
		
			
			|  | 886 | +     * As native permissions are asynchronously handled by the PermissionListener, many fields have
 | 
	
		
			
			|  | 887 | +     * to be stored to send permissions results to the webview
 | 
	
		
			
			|  | 888 | +     */
 | 
	
		
			
			|  | 889 | +
 | 
	
		
			
			|  | 890 | +    // Webview camera & audio permission callback
 | 
	
		
			
			|  | 891 | +    protected PermissionRequest permissionRequest;
 | 
	
		
			
			|  | 892 | +    // Webview camera & audio permission already granted
 | 
	
		
			
			|  | 893 | +    protected ArrayList<String> grantedPermissions;
 | 
	
		
			
			|  | 894 | +
 | 
	
		
			
			|  | 895 | +    // Webview geolocation permission callback
 | 
	
		
			
			|  | 896 | +    protected GeolocationPermissions.Callback geolocationPermissionCallback;
 | 
	
		
			
			|  | 897 | +    // Webview geolocation permission origin callback
 | 
	
		
			
			|  | 898 | +    protected String geolocationPermissionOrigin;
 | 
	
		
			
			|  | 899 | +
 | 
	
		
			
			|  | 900 | +    // true if native permissions dialog is shown, false otherwise
 | 
	
		
			
			|  | 901 | +    protected boolean permissionsRequestShown = false;
 | 
	
		
			
			|  | 902 | +    // Pending Android permissions for the next request
 | 
	
		
			
			|  | 903 | +    protected ArrayList<String> pendingPermissions = new ArrayList<>();
 | 
	
		
			
			|  | 904 | +
 | 
	
		
			
			|  | 905 | +
 | 
	
		
			
			| 877 | 906 |      public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
 | 
	
		
			
			| 878 | 907 |        this.mReactContext = reactContext;
 | 
	
		
			
			| 879 | 908 |        this.mWebView = webView;
 | 
	
	
		
			
			|  | @@ -888,42 +917,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
 | 
	
		
			
			| 888 | 917 |        return true;
 | 
	
		
			
			| 889 | 918 |      }
 | 
	
		
			
			| 890 | 919 |  
 | 
	
		
			
			| 891 |  | -    // Fix WebRTC permission request error.
 | 
	
		
			
			| 892 |  | -    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 | 
	
		
			
			| 893 |  | -    @Override
 | 
	
		
			
			| 894 |  | -    public void onPermissionRequest(final PermissionRequest request) {
 | 
	
		
			
			| 895 |  | -      String[] requestedResources = request.getResources();
 | 
	
		
			
			| 896 |  | -      ArrayList<String> permissions = new ArrayList<>();
 | 
	
		
			
			| 897 |  | -      ArrayList<String> grantedPermissions = new ArrayList<String>();
 | 
	
		
			
			| 898 |  | -      for (int i = 0; i < requestedResources.length; i++) {
 | 
	
		
			
			| 899 |  | -        if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
 | 
	
		
			
			| 900 |  | -          permissions.add(Manifest.permission.RECORD_AUDIO);
 | 
	
		
			
			| 901 |  | -        } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
 | 
	
		
			
			| 902 |  | -          permissions.add(Manifest.permission.CAMERA);
 | 
	
		
			
			| 903 |  | -        }
 | 
	
		
			
			| 904 |  | -        // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
 | 
	
		
			
			| 905 |  | -      }
 | 
	
		
			
			| 906 |  | -
 | 
	
		
			
			| 907 |  | -      for (int i = 0; i < permissions.size(); i++) {
 | 
	
		
			
			| 908 |  | -        if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
 | 
	
		
			
			| 909 |  | -          continue;
 | 
	
		
			
			| 910 |  | -        }
 | 
	
		
			
			| 911 |  | -        if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
 | 
	
		
			
			| 912 |  | -          grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
 | 
	
		
			
			| 913 |  | -        } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
 | 
	
		
			
			| 914 |  | -          grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
 | 
	
		
			
			| 915 |  | -        }
 | 
	
		
			
			| 916 |  | -      }
 | 
	
		
			
			| 917 |  | -
 | 
	
		
			
			| 918 |  | -      if (grantedPermissions.isEmpty()) {
 | 
	
		
			
			| 919 |  | -        request.deny();
 | 
	
		
			
			| 920 |  | -      } else {
 | 
	
		
			
			| 921 |  | -        String[] grantedPermissionsArray = new String[grantedPermissions.size()];
 | 
	
		
			
			| 922 |  | -        grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
 | 
	
		
			
			| 923 |  | -        request.grant(grantedPermissionsArray);
 | 
	
		
			
			| 924 |  | -      }
 | 
	
		
			
			| 925 |  | -    }
 | 
	
		
			
			| 926 |  | -
 | 
	
		
			
			| 927 | 920 |      @Override
 | 
	
		
			
			| 928 | 921 |      public void onProgressChanged(WebView webView, int newProgress) {
 | 
	
		
			
			| 929 | 922 |        super.onProgressChanged(webView, newProgress);
 | 
	
	
		
			
			|  | @@ -949,11 +942,164 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
 | 
	
		
			
			| 949 | 942 |            event));
 | 
	
		
			
			| 950 | 943 |      }
 | 
	
		
			
			| 951 | 944 |  
 | 
	
		
			
			|  | 945 | +    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 | 
	
		
			
			|  | 946 | +    @Override
 | 
	
		
			
			|  | 947 | +    public void onPermissionRequest(final PermissionRequest request) {
 | 
	
		
			
			|  | 948 | +
 | 
	
		
			
			|  | 949 | +      grantedPermissions = new ArrayList<>();
 | 
	
		
			
			|  | 950 | +
 | 
	
		
			
			|  | 951 | +      ArrayList<String> requestedAndroidPermissions = new ArrayList<>();
 | 
	
		
			
			|  | 952 | +      for (String requestedResource : request.getResources()) {
 | 
	
		
			
			|  | 953 | +        String androidPermission = null;
 | 
	
		
			
			|  | 954 | +
 | 
	
		
			
			|  | 955 | +        if (requestedResource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
 | 
	
		
			
			|  | 956 | +          androidPermission = Manifest.permission.RECORD_AUDIO;
 | 
	
		
			
			|  | 957 | +        } else if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
 | 
	
		
			
			|  | 958 | +          androidPermission = Manifest.permission.CAMERA;
 | 
	
		
			
			|  | 959 | +        }
 | 
	
		
			
			|  | 960 | +        // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
 | 
	
		
			
			|  | 961 | +
 | 
	
		
			
			|  | 962 | +        if (androidPermission != null) {
 | 
	
		
			
			|  | 963 | +          if (ContextCompat.checkSelfPermission(mReactContext, androidPermission) == PackageManager.PERMISSION_GRANTED) {
 | 
	
		
			
			|  | 964 | +            grantedPermissions.add(requestedResource);
 | 
	
		
			
			|  | 965 | +          } else {
 | 
	
		
			
			|  | 966 | +            requestedAndroidPermissions.add(androidPermission);
 | 
	
		
			
			|  | 967 | +          }
 | 
	
		
			
			|  | 968 | +        }
 | 
	
		
			
			|  | 969 | +      }
 | 
	
		
			
			|  | 970 | +
 | 
	
		
			
			|  | 971 | +      // If all the permissions are already granted, send the response to the WebView synchronously
 | 
	
		
			
			|  | 972 | +      if (requestedAndroidPermissions.isEmpty()) {
 | 
	
		
			
			|  | 973 | +        request.grant(grantedPermissions.toArray(new String[0]));
 | 
	
		
			
			|  | 974 | +        grantedPermissions = null;
 | 
	
		
			
			|  | 975 | +        return;
 | 
	
		
			
			|  | 976 | +      }
 | 
	
		
			
			|  | 977 | +
 | 
	
		
			
			|  | 978 | +      // Otherwise, ask to Android System for native permissions asynchronously
 | 
	
		
			
			|  | 979 | +
 | 
	
		
			
			|  | 980 | +      this.permissionRequest = request;
 | 
	
		
			
			|  | 981 | +
 | 
	
		
			
			|  | 982 | +      requestPermissions(requestedAndroidPermissions);
 | 
	
		
			
			|  | 983 | +    }
 | 
	
		
			
			|  | 984 | +
 | 
	
		
			
			|  | 985 | +
 | 
	
		
			
			| 952 | 986 |      @Override
 | 
	
		
			
			| 953 | 987 |      public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
 | 
	
		
			
			| 954 |  | -      callback.invoke(origin, true, false);
 | 
	
		
			
			|  | 988 | +
 | 
	
		
			
			|  | 989 | +      if (ContextCompat.checkSelfPermission(mReactContext, Manifest.permission.ACCESS_FINE_LOCATION)
 | 
	
		
			
			|  | 990 | +        != PackageManager.PERMISSION_GRANTED) {
 | 
	
		
			
			|  | 991 | +
 | 
	
		
			
			|  | 992 | +        /*
 | 
	
		
			
			|  | 993 | +         * Keep the trace of callback and origin for the async permission request
 | 
	
		
			
			|  | 994 | +         */
 | 
	
		
			
			|  | 995 | +        geolocationPermissionCallback = callback;
 | 
	
		
			
			|  | 996 | +        geolocationPermissionOrigin = origin;
 | 
	
		
			
			|  | 997 | +
 | 
	
		
			
			|  | 998 | +        requestPermissions(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION));
 | 
	
		
			
			|  | 999 | +
 | 
	
		
			
			|  | 1000 | +      } else {
 | 
	
		
			
			|  | 1001 | +        callback.invoke(origin, true, true);
 | 
	
		
			
			|  | 1002 | +      }
 | 
	
		
			
			|  | 1003 | +    }
 | 
	
		
			
			|  | 1004 | +
 | 
	
		
			
			|  | 1005 | +    private PermissionAwareActivity getPermissionAwareActivity() {
 | 
	
		
			
			|  | 1006 | +      Activity activity = mReactContext.getCurrentActivity();
 | 
	
		
			
			|  | 1007 | +      if (activity == null) {
 | 
	
		
			
			|  | 1008 | +        throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
 | 
	
		
			
			|  | 1009 | +      } else if (!(activity instanceof PermissionAwareActivity)) {
 | 
	
		
			
			|  | 1010 | +        throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
 | 
	
		
			
			|  | 1011 | +      }
 | 
	
		
			
			|  | 1012 | +      return (PermissionAwareActivity) activity;
 | 
	
		
			
			|  | 1013 | +    }
 | 
	
		
			
			|  | 1014 | +
 | 
	
		
			
			|  | 1015 | +    private synchronized void requestPermissions(List<String> permissions) {
 | 
	
		
			
			|  | 1016 | +
 | 
	
		
			
			|  | 1017 | +      /*
 | 
	
		
			
			|  | 1018 | +       * If permissions request dialog is displayed on the screen and another request is sent to the
 | 
	
		
			
			|  | 1019 | +       * activity, the last permission asked is skipped. As a work-around, we use pendingPermissions
 | 
	
		
			
			|  | 1020 | +       * to store next required permissions.
 | 
	
		
			
			|  | 1021 | +       */
 | 
	
		
			
			|  | 1022 | +
 | 
	
		
			
			|  | 1023 | +      if (permissionsRequestShown) {
 | 
	
		
			
			|  | 1024 | +        pendingPermissions.addAll(permissions);
 | 
	
		
			
			|  | 1025 | +        return;
 | 
	
		
			
			|  | 1026 | +      }
 | 
	
		
			
			|  | 1027 | +
 | 
	
		
			
			|  | 1028 | +      PermissionAwareActivity activity = getPermissionAwareActivity();
 | 
	
		
			
			|  | 1029 | +      permissionsRequestShown = true;
 | 
	
		
			
			|  | 1030 | +
 | 
	
		
			
			|  | 1031 | +      activity.requestPermissions(
 | 
	
		
			
			|  | 1032 | +        permissions.toArray(new String[0]),
 | 
	
		
			
			|  | 1033 | +        COMMON_PERMISSION_REQUEST,
 | 
	
		
			
			|  | 1034 | +        webviewPermissionsListener
 | 
	
		
			
			|  | 1035 | +      );
 | 
	
		
			
			|  | 1036 | +
 | 
	
		
			
			|  | 1037 | +      // Pending permissions have been sent, the list can be cleared
 | 
	
		
			
			|  | 1038 | +      pendingPermissions.clear();
 | 
	
		
			
			| 955 | 1039 |      }
 | 
	
		
			
			| 956 | 1040 |  
 | 
	
		
			
			|  | 1041 | +
 | 
	
		
			
			|  | 1042 | +    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 | 
	
		
			
			|  | 1043 | +    private PermissionListener webviewPermissionsListener = (requestCode, permissions, grantResults) -> {
 | 
	
		
			
			|  | 1044 | +
 | 
	
		
			
			|  | 1045 | +      permissionsRequestShown = false;
 | 
	
		
			
			|  | 1046 | +
 | 
	
		
			
			|  | 1047 | +      /*
 | 
	
		
			
			|  | 1048 | +       * As a "pending requests" approach is used, requestCode cannot help to define if the request
 | 
	
		
			
			|  | 1049 | +       * came from geolocation or camera/audio. This is why shouldAnswerToPermissionRequest is used
 | 
	
		
			
			|  | 1050 | +       */
 | 
	
		
			
			|  | 1051 | +      boolean shouldAnswerToPermissionRequest = false;
 | 
	
		
			
			|  | 1052 | +
 | 
	
		
			
			|  | 1053 | +      for (int i = 0; i < permissions.length; i++) {
 | 
	
		
			
			|  | 1054 | +
 | 
	
		
			
			|  | 1055 | +        String permission = permissions[i];
 | 
	
		
			
			|  | 1056 | +        boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
 | 
	
		
			
			|  | 1057 | +
 | 
	
		
			
			|  | 1058 | +        if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)
 | 
	
		
			
			|  | 1059 | +          && geolocationPermissionCallback != null
 | 
	
		
			
			|  | 1060 | +          && geolocationPermissionOrigin != null) {
 | 
	
		
			
			|  | 1061 | +
 | 
	
		
			
			|  | 1062 | +          if (granted) {
 | 
	
		
			
			|  | 1063 | +            geolocationPermissionCallback.invoke(geolocationPermissionOrigin, true, true);
 | 
	
		
			
			|  | 1064 | +          } else {
 | 
	
		
			
			|  | 1065 | +            geolocationPermissionCallback.invoke(geolocationPermissionOrigin, false, false);
 | 
	
		
			
			|  | 1066 | +          }
 | 
	
		
			
			|  | 1067 | +
 | 
	
		
			
			|  | 1068 | +          geolocationPermissionCallback = null;
 | 
	
		
			
			|  | 1069 | +          geolocationPermissionOrigin = null;
 | 
	
		
			
			|  | 1070 | +        }
 | 
	
		
			
			|  | 1071 | +
 | 
	
		
			
			|  | 1072 | +        if (permission.equals(Manifest.permission.RECORD_AUDIO)) {
 | 
	
		
			
			|  | 1073 | +          if (granted && grantedPermissions != null) {
 | 
	
		
			
			|  | 1074 | +            grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
 | 
	
		
			
			|  | 1075 | +          }
 | 
	
		
			
			|  | 1076 | +          shouldAnswerToPermissionRequest = true;
 | 
	
		
			
			|  | 1077 | +        }
 | 
	
		
			
			|  | 1078 | +
 | 
	
		
			
			|  | 1079 | +        if (permission.equals(Manifest.permission.CAMERA)) {
 | 
	
		
			
			|  | 1080 | +          if (granted && grantedPermissions != null) {
 | 
	
		
			
			|  | 1081 | +            grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
 | 
	
		
			
			|  | 1082 | +          }
 | 
	
		
			
			|  | 1083 | +          shouldAnswerToPermissionRequest = true;
 | 
	
		
			
			|  | 1084 | +        }
 | 
	
		
			
			|  | 1085 | +      }
 | 
	
		
			
			|  | 1086 | +
 | 
	
		
			
			|  | 1087 | +      if (shouldAnswerToPermissionRequest
 | 
	
		
			
			|  | 1088 | +        && permissionRequest != null
 | 
	
		
			
			|  | 1089 | +        && grantedPermissions != null) {
 | 
	
		
			
			|  | 1090 | +        permissionRequest.grant(grantedPermissions.toArray(new String[0]));
 | 
	
		
			
			|  | 1091 | +        permissionRequest = null;
 | 
	
		
			
			|  | 1092 | +        grantedPermissions = null;
 | 
	
		
			
			|  | 1093 | +      }
 | 
	
		
			
			|  | 1094 | +
 | 
	
		
			
			|  | 1095 | +      if (!pendingPermissions.isEmpty()) {
 | 
	
		
			
			|  | 1096 | +        requestPermissions(pendingPermissions);
 | 
	
		
			
			|  | 1097 | +        return false;
 | 
	
		
			
			|  | 1098 | +      }
 | 
	
		
			
			|  | 1099 | +
 | 
	
		
			
			|  | 1100 | +      return true;
 | 
	
		
			
			|  | 1101 | +    };
 | 
	
		
			
			|  | 1102 | +
 | 
	
		
			
			| 957 | 1103 |      protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
 | 
	
		
			
			| 958 | 1104 |        getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
 | 
	
		
			
			| 959 | 1105 |      }
 |