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