Thibaud Michel 4 years ago
parent
commit
1cf4f1a587
No account linked to committer's email address

+ 182
- 37
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java View File

2
 
2
 
3
 import android.annotation.SuppressLint;
3
 import android.annotation.SuppressLint;
4
 import android.annotation.TargetApi;
4
 import android.annotation.TargetApi;
5
+import android.app.Activity;
5
 import android.app.DownloadManager;
6
 import android.app.DownloadManager;
6
 import android.content.Context;
7
 import android.content.Context;
7
 import android.content.pm.ActivityInfo;
8
 import android.content.pm.ActivityInfo;
45
 import androidx.core.util.Pair;
46
 import androidx.core.util.Pair;
46
 
47
 
47
 import com.facebook.common.logging.FLog;
48
 import com.facebook.common.logging.FLog;
49
+import com.facebook.react.modules.core.PermissionAwareActivity;
50
+import com.facebook.react.modules.core.PermissionListener;
48
 import com.facebook.react.views.scroll.ScrollEvent;
51
 import com.facebook.react.views.scroll.ScrollEvent;
49
 import com.facebook.react.views.scroll.ScrollEventType;
52
 import com.facebook.react.views.scroll.ScrollEventType;
50
 import com.facebook.react.views.scroll.OnScrollDispatchHelper;
53
 import com.facebook.react.views.scroll.OnScrollDispatchHelper;
86
 import java.net.URL;
89
 import java.net.URL;
87
 import java.net.URLEncoder;
90
 import java.net.URLEncoder;
88
 import java.util.ArrayList;
91
 import java.util.ArrayList;
92
+import java.util.Collections;
89
 import java.util.HashMap;
93
 import java.util.HashMap;
94
+import java.util.List;
90
 import java.util.Locale;
95
 import java.util.Locale;
91
 import java.util.Map;
96
 import java.util.Map;
92
 import java.util.concurrent.atomic.AtomicReference;
97
 import java.util.concurrent.atomic.AtomicReference;
1044
       View.SYSTEM_UI_FLAG_IMMERSIVE |
1049
       View.SYSTEM_UI_FLAG_IMMERSIVE |
1045
       View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
1050
       View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
1046
 
1051
 
1052
+    protected static final int COMMON_PERMISSION_REQUEST = 3;
1053
+
1047
     protected ReactContext mReactContext;
1054
     protected ReactContext mReactContext;
1048
     protected View mWebView;
1055
     protected View mWebView;
1049
 
1056
 
1050
     protected View mVideoView;
1057
     protected View mVideoView;
1051
     protected WebChromeClient.CustomViewCallback mCustomViewCallback;
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
     protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
1081
     protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
1054
 
1082
 
1055
     public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
1083
     public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
1066
       return true;
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
     @Override
1097
     @Override
1106
     public void onProgressChanged(WebView webView, int newProgress) {
1098
     public void onProgressChanged(WebView webView, int newProgress) {
1107
       super.onProgressChanged(webView, newProgress);
1099
       super.onProgressChanged(webView, newProgress);
1123
           event));
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
     @Override
1159
     @Override
1127
     public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
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
     protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
1276
     protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
1132
       getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
1277
       getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
1133
     }
1278
     }