Thibaud Michel 4 年 前
コミット
1cf4f1a587
No account linked to committer's email address
共有1 個のファイルを変更した182 個の追加37 個の削除を含む
  1. 182
    37
      android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java

+ 182
- 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.pm.ActivityInfo;
@@ -45,6 +46,8 @@ import androidx.core.content.ContextCompat;
45 46
 import androidx.core.util.Pair;
46 47
 
47 48
 import com.facebook.common.logging.FLog;
49
+import com.facebook.react.modules.core.PermissionAwareActivity;
50
+import com.facebook.react.modules.core.PermissionListener;
48 51
 import com.facebook.react.views.scroll.ScrollEvent;
49 52
 import com.facebook.react.views.scroll.ScrollEventType;
50 53
 import com.facebook.react.views.scroll.OnScrollDispatchHelper;
@@ -86,7 +89,9 @@ import java.net.MalformedURLException;
86 89
 import java.net.URL;
87 90
 import java.net.URLEncoder;
88 91
 import java.util.ArrayList;
92
+import java.util.Collections;
89 93
 import java.util.HashMap;
94
+import java.util.List;
90 95
 import java.util.Locale;
91 96
 import java.util.Map;
92 97
 import java.util.concurrent.atomic.AtomicReference;
@@ -1044,12 +1049,35 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
1044 1049
       View.SYSTEM_UI_FLAG_IMMERSIVE |
1045 1050
       View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
1046 1051
 
1052
+    protected static final int COMMON_PERMISSION_REQUEST = 3;
1053
+
1047 1054
     protected ReactContext mReactContext;
1048 1055
     protected View mWebView;
1049 1056
 
1050 1057
     protected View mVideoView;
1051 1058
     protected WebChromeClient.CustomViewCallback mCustomViewCallback;
1052 1059
 
1060
+    /*
1061
+     * - Permissions -
1062
+     * As native permissions are asynchronously handled by the PermissionListener, many fields have
1063
+     * to be stored to send permissions results to the webview
1064
+     */
1065
+
1066
+    // Webview camera & audio permission callback
1067
+    protected PermissionRequest permissionRequest;
1068
+    // Webview camera & audio permission already granted
1069
+    protected ArrayList<String> grantedPermissions;
1070
+
1071
+    // Webview geolocation permission callback
1072
+    protected GeolocationPermissions.Callback geolocationPermissionCallback;
1073
+    // Webview geolocation permission origin callback
1074
+    protected String geolocationPermissionOrigin;
1075
+
1076
+    // true if native permissions dialog is shown, false otherwise
1077
+    protected boolean permissionsRequestShown = false;
1078
+    // Pending Android permissions for the next request
1079
+    protected ArrayList<String> pendingPermissions = new ArrayList<>();
1080
+
1053 1081
     protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
1054 1082
 
1055 1083
     public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
@@ -1066,42 +1094,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
1066 1094
       return true;
1067 1095
     }
1068 1096
 
1069
-    // Fix WebRTC permission request error.
1070
-    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1071
-    @Override
1072
-    public void onPermissionRequest(final PermissionRequest request) {
1073
-      String[] requestedResources = request.getResources();
1074
-      ArrayList<String> permissions = new ArrayList<>();
1075
-      ArrayList<String> grantedPermissions = new ArrayList<String>();
1076
-      for (int i = 0; i < requestedResources.length; i++) {
1077
-        if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
1078
-          permissions.add(Manifest.permission.RECORD_AUDIO);
1079
-        } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
1080
-          permissions.add(Manifest.permission.CAMERA);
1081
-        }
1082
-        // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
1083
-      }
1084
-
1085
-      for (int i = 0; i < permissions.size(); i++) {
1086
-        if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
1087
-          continue;
1088
-        }
1089
-        if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
1090
-          grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
1091
-        } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
1092
-          grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
1093
-        }
1094
-      }
1095
-
1096
-      if (grantedPermissions.isEmpty()) {
1097
-        request.deny();
1098
-      } else {
1099
-        String[] grantedPermissionsArray = new String[grantedPermissions.size()];
1100
-        grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
1101
-        request.grant(grantedPermissionsArray);
1102
-      }
1103
-    }
1104
-
1105 1097
     @Override
1106 1098
     public void onProgressChanged(WebView webView, int newProgress) {
1107 1099
       super.onProgressChanged(webView, newProgress);
@@ -1123,11 +1115,164 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
1123 1115
           event));
1124 1116
     }
1125 1117
 
1118
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1119
+    @Override
1120
+    public void onPermissionRequest(final PermissionRequest request) {
1121
+
1122
+      grantedPermissions = new ArrayList<>();
1123
+
1124
+      ArrayList<String> requestedAndroidPermissions = new ArrayList<>();
1125
+      for (String requestedResource : request.getResources()) {
1126
+        String androidPermission = null;
1127
+
1128
+        if (requestedResource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
1129
+          androidPermission = Manifest.permission.RECORD_AUDIO;
1130
+        } else if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
1131
+          androidPermission = Manifest.permission.CAMERA;
1132
+        }
1133
+        // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
1134
+
1135
+        if (androidPermission != null) {
1136
+          if (ContextCompat.checkSelfPermission(mReactContext, androidPermission) == PackageManager.PERMISSION_GRANTED) {
1137
+            grantedPermissions.add(requestedResource);
1138
+          } else {
1139
+            requestedAndroidPermissions.add(androidPermission);
1140
+          }
1141
+        }
1142
+      }
1143
+
1144
+      // If all the permissions are already granted, send the response to the WebView synchronously
1145
+      if (requestedAndroidPermissions.isEmpty()) {
1146
+        request.grant(grantedPermissions.toArray(new String[0]));
1147
+        grantedPermissions = null;
1148
+        return;
1149
+      }
1150
+
1151
+      // Otherwise, ask to Android System for native permissions asynchronously
1152
+
1153
+      this.permissionRequest = request;
1154
+
1155
+      requestPermissions(requestedAndroidPermissions);
1156
+    }
1157
+
1158
+
1126 1159
     @Override
1127 1160
     public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
1128
-      callback.invoke(origin, true, false);
1161
+
1162
+      if (ContextCompat.checkSelfPermission(mReactContext, Manifest.permission.ACCESS_FINE_LOCATION)
1163
+        != PackageManager.PERMISSION_GRANTED) {
1164
+
1165
+        /*
1166
+         * Keep the trace of callback and origin for the async permission request
1167
+         */
1168
+        geolocationPermissionCallback = callback;
1169
+        geolocationPermissionOrigin = origin;
1170
+
1171
+        requestPermissions(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION));
1172
+
1173
+      } else {
1174
+        callback.invoke(origin, true, true);
1175
+      }
1176
+    }
1177
+
1178
+    private PermissionAwareActivity getPermissionAwareActivity() {
1179
+      Activity activity = mReactContext.getCurrentActivity();
1180
+      if (activity == null) {
1181
+        throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
1182
+      } else if (!(activity instanceof PermissionAwareActivity)) {
1183
+        throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
1184
+      }
1185
+      return (PermissionAwareActivity) activity;
1186
+    }
1187
+
1188
+    private synchronized void requestPermissions(List<String> permissions) {
1189
+
1190
+      /*
1191
+       * If permissions request dialog is displayed on the screen and another request is sent to the
1192
+       * activity, the last permission asked is skipped. As a work-around, we use pendingPermissions
1193
+       * to store next required permissions.
1194
+       */
1195
+
1196
+      if (permissionsRequestShown) {
1197
+        pendingPermissions.addAll(permissions);
1198
+        return;
1199
+      }
1200
+
1201
+      PermissionAwareActivity activity = getPermissionAwareActivity();
1202
+      permissionsRequestShown = true;
1203
+
1204
+      activity.requestPermissions(
1205
+        permissions.toArray(new String[0]),
1206
+        COMMON_PERMISSION_REQUEST,
1207
+        webviewPermissionsListener
1208
+      );
1209
+
1210
+      // Pending permissions have been sent, the list can be cleared
1211
+      pendingPermissions.clear();
1129 1212
     }
1130 1213
 
1214
+
1215
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
1216
+    private PermissionListener webviewPermissionsListener = (requestCode, permissions, grantResults) -> {
1217
+
1218
+      permissionsRequestShown = false;
1219
+
1220
+      /*
1221
+       * As a "pending requests" approach is used, requestCode cannot help to define if the request
1222
+       * came from geolocation or camera/audio. This is why shouldAnswerToPermissionRequest is used
1223
+       */
1224
+      boolean shouldAnswerToPermissionRequest = false;
1225
+
1226
+      for (int i = 0; i < permissions.length; i++) {
1227
+
1228
+        String permission = permissions[i];
1229
+        boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
1230
+
1231
+        if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)
1232
+          && geolocationPermissionCallback != null
1233
+          && geolocationPermissionOrigin != null) {
1234
+
1235
+          if (granted) {
1236
+            geolocationPermissionCallback.invoke(geolocationPermissionOrigin, true, true);
1237
+          } else {
1238
+            geolocationPermissionCallback.invoke(geolocationPermissionOrigin, false, false);
1239
+          }
1240
+
1241
+          geolocationPermissionCallback = null;
1242
+          geolocationPermissionOrigin = null;
1243
+        }
1244
+
1245
+        if (permission.equals(Manifest.permission.RECORD_AUDIO)) {
1246
+          if (granted && grantedPermissions != null) {
1247
+            grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
1248
+          }
1249
+          shouldAnswerToPermissionRequest = true;
1250
+        }
1251
+
1252
+        if (permission.equals(Manifest.permission.CAMERA)) {
1253
+          if (granted && grantedPermissions != null) {
1254
+            grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
1255
+          }
1256
+          shouldAnswerToPermissionRequest = true;
1257
+        }
1258
+      }
1259
+
1260
+      if (shouldAnswerToPermissionRequest
1261
+        && permissionRequest != null
1262
+        && grantedPermissions != null) {
1263
+        permissionRequest.grant(grantedPermissions.toArray(new String[0]));
1264
+        permissionRequest = null;
1265
+        grantedPermissions = null;
1266
+      }
1267
+
1268
+      if (!pendingPermissions.isEmpty()) {
1269
+        requestPermissions(pendingPermissions);
1270
+        return false;
1271
+      }
1272
+
1273
+      return true;
1274
+    };
1275
+
1131 1276
     protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
1132 1277
       getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
1133 1278
     }