Просмотр исходного кода

Enhanced camera/audio and geolocation permissions

Thibaud 5 лет назад
Родитель
Сommit
744228e261
1 измененных файлов: 183 добавлений и 37 удалений
  1. 183
    37
      android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java

+ 183
- 37
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java Просмотреть файл

@@ -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
     }