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