feat(android): Enhanced camera/audio and geolocation permissions (#1239)
This commit is contained in:
parent
0419e32f3f
commit
9f6037e4b2
|
@ -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.pm.ActivityInfo;
|
||||
|
@ -46,6 +47,8 @@ import androidx.core.content.ContextCompat;
|
|||
import androidx.core.util.Pair;
|
||||
|
||||
import com.facebook.common.logging.FLog;
|
||||
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;
|
||||
|
@ -86,7 +89,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;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -1086,12 +1091,35 @@ 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;
|
||||
|
||||
protected View mVideoView;
|
||||
protected WebChromeClient.CustomViewCallback mCustomViewCallback;
|
||||
|
||||
/*
|
||||
* - 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 List<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 List<String> pendingPermissions = new ArrayList<>();
|
||||
|
||||
protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
|
||||
|
||||
public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
|
||||
|
@ -1180,11 +1208,164 @@ 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, false);
|
||||
}
|
||||
}
|
||||
|
||||
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, false);
|
||||
} 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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue