Skip to content

Instantly share code, notes, and snippets.

@TPXP
Created July 22, 2020 07:34
Show Gist options
  • Save TPXP/5ec918850dd40bd46203c41e98e61003 to your computer and use it in GitHub Desktop.
Save TPXP/5ec918850dd40bd46203c41e98e61003 to your computer and use it in GitHub Desktop.
diff --git a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
index ab869cf..e37d88d 100644
--- a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
+++ b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
@@ -2,6 +2,7 @@ package com.reactnativecommunity.webview;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
+import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
@@ -39,6 +40,8 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
+import com.facebook.react.modules.core.PermissionAwareActivity;
+import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.views.scroll.ScrollEvent;
import com.facebook.react.views.scroll.ScrollEventType;
import com.facebook.react.views.scroll.OnScrollDispatchHelper;
@@ -78,7 +81,9 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -942,6 +947,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ protected static final int COMMON_PERMISSION_REQUEST = 3;
+
protected ReactContext mReactContext;
protected View mWebView;
@@ -950,6 +957,27 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
+ /*
+ * - Permissions -
+ * As native permissions are asynchronously handled by the PermissionListener, many fields have
+ * to be stored to send permissions results to the webview
+ */
+
+ // Webview camera & audio permission callback
+ protected PermissionRequest permissionRequest;
+ // Webview camera & audio permission already granted
+ protected ArrayList<String> grantedPermissions;
+
+ // Webview geolocation permission callback
+ protected GeolocationPermissions.Callback geolocationPermissionCallback;
+ // Webview geolocation permission origin callback
+ protected String geolocationPermissionOrigin;
+
+ // true if native permissions dialog is shown, false otherwise
+ protected boolean permissionsRequestShown = false;
+ // Pending Android permissions for the next request
+ protected ArrayList<String> pendingPermissions = new ArrayList<>();
+
public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
this.mReactContext = reactContext;
this.mWebView = webView;
@@ -964,42 +992,6 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
return true;
}
- // Fix WebRTC permission request error.
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- @Override
- public void onPermissionRequest(final PermissionRequest request) {
- String[] requestedResources = request.getResources();
- ArrayList<String> permissions = new ArrayList<>();
- ArrayList<String> grantedPermissions = new ArrayList<String>();
- for (int i = 0; i < requestedResources.length; i++) {
- if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
- permissions.add(Manifest.permission.RECORD_AUDIO);
- } else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
- permissions.add(Manifest.permission.CAMERA);
- }
- // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
- }
-
- for (int i = 0; i < permissions.size(); i++) {
- if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
- if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
- grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
- } else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
- grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
- }
- }
-
- if (grantedPermissions.isEmpty()) {
- request.deny();
- } else {
- String[] grantedPermissionsArray = new String[grantedPermissions.size()];
- grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
- request.grant(grantedPermissionsArray);
- }
- }
-
@Override
public void onProgressChanged(WebView webView, int newProgress) {
super.onProgressChanged(webView, newProgress);
@@ -1021,11 +1013,163 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
event));
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void onPermissionRequest(final PermissionRequest request) {
+
+ grantedPermissions = new ArrayList<>();
+
+ ArrayList<String> requestedAndroidPermissions = new ArrayList<>();
+ for (String requestedResource : request.getResources()) {
+ String androidPermission = null;
+
+ if (requestedResource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
+ androidPermission = Manifest.permission.RECORD_AUDIO;
+ } else if (requestedResource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
+ androidPermission = Manifest.permission.CAMERA;
+ }
+ // TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
+
+ if (androidPermission != null) {
+ if (ContextCompat.checkSelfPermission(mReactContext, androidPermission) == PackageManager.PERMISSION_GRANTED) {
+ grantedPermissions.add(requestedResource);
+ } else {
+ requestedAndroidPermissions.add(androidPermission);
+ }
+ }
+ }
+
+ // If all the permissions are already granted, send the response to the WebView synchronously
+ if (requestedAndroidPermissions.isEmpty()) {
+ request.grant(grantedPermissions.toArray(new String[0]));
+ grantedPermissions = null;
+ return;
+ }
+
+ // Otherwise, ask to Android System for native permissions asynchronously
+
+ this.permissionRequest = request;
+
+ requestPermissions(requestedAndroidPermissions);
+ }
+
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
- callback.invoke(origin, true, false);
+
+
+ if (ContextCompat.checkSelfPermission(mReactContext, Manifest.permission.ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ /*
+ * Keep the trace of callback and origin for the async permission request
+ */
+ geolocationPermissionCallback = callback;
+ geolocationPermissionOrigin = origin;
+
+ requestPermissions(Collections.singletonList(Manifest.permission.ACCESS_FINE_LOCATION));
+
+ } else {
+ callback.invoke(origin, true, true);
+ }
+ }
+
+ private PermissionAwareActivity getPermissionAwareActivity() {
+ Activity activity = mReactContext.getCurrentActivity();
+ if (activity == null) {
+ throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
+ } else if (!(activity instanceof PermissionAwareActivity)) {
+ throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
+ }
+ return (PermissionAwareActivity) activity;
+ }
+
+ private synchronized void requestPermissions(List<String> permissions) {
+
+ /*
+ * If permissions request dialog is displayed on the screen and another request is sent to the
+ * activity, the last permission asked is skipped. As a work-around, we use pendingPermissions
+ * to store next required permissions.
+ */
+
+ if (permissionsRequestShown) {
+ pendingPermissions.addAll(permissions);
+ return;
+ }
+
+ PermissionAwareActivity activity = getPermissionAwareActivity();
+ permissionsRequestShown = true;
+
+ activity.requestPermissions(
+ permissions.toArray(new String[0]),
+ COMMON_PERMISSION_REQUEST,
+ webviewPermissionsListener
+ );
+
+ // Pending permissions have been sent, the list can be cleared
+ pendingPermissions.clear();
}
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private PermissionListener webviewPermissionsListener = (requestCode, permissions, grantResults) -> {
+
+ permissionsRequestShown = false;
+
+ /*
+ * As a "pending requests" approach is used, requestCode cannot help to define if the request
+ * came from geolocation or camera/audio. This is why shouldAnswerToPermissionRequest is used
+ */
+ boolean shouldAnswerToPermissionRequest = false;
+
+ for (int i = 0; i < permissions.length; i++) {
+
+ String permission = permissions[i];
+ boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED;
+
+ if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)
+ && geolocationPermissionCallback != null
+ && geolocationPermissionOrigin != null) {
+
+ if (granted) {
+ geolocationPermissionCallback.invoke(geolocationPermissionOrigin, true, true);
+ } else {
+ geolocationPermissionCallback.invoke(geolocationPermissionOrigin, false, false);
+ }
+
+ geolocationPermissionCallback = null;
+ geolocationPermissionOrigin = null;
+ }
+
+ if (permission.equals(Manifest.permission.RECORD_AUDIO)) {
+ if (granted && grantedPermissions != null) {
+ grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
+ }
+ shouldAnswerToPermissionRequest = true;
+ }
+
+ if (permission.equals(Manifest.permission.CAMERA)) {
+ if (granted && grantedPermissions != null) {
+ grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
+ }
+ shouldAnswerToPermissionRequest = true;
+ }
+ }
+
+ if (shouldAnswerToPermissionRequest
+ && permissionRequest != null
+ && grantedPermissions != null) {
+ permissionRequest.grant(grantedPermissions.toArray(new String[0]));
+ permissionRequest = null;
+ grantedPermissions = null;
+ }
+
+ if (!pendingPermissions.isEmpty()) {
+ requestPermissions(pendingPermissions);
+ return false;
+ }
+
+ return true;
+ };
+
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment