@capacitor-plus/android 8.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/capacitor/build.gradle +94 -0
  3. package/capacitor/lint-baseline.xml +136 -0
  4. package/capacitor/lint.xml +9 -0
  5. package/capacitor/proguard-rules.pro +28 -0
  6. package/capacitor/src/main/AndroidManifest.xml +3 -0
  7. package/capacitor/src/main/assets/native-bridge.js +1029 -0
  8. package/capacitor/src/main/java/com/getcapacitor/AndroidProtocolHandler.java +94 -0
  9. package/capacitor/src/main/java/com/getcapacitor/App.java +61 -0
  10. package/capacitor/src/main/java/com/getcapacitor/AppUUID.java +65 -0
  11. package/capacitor/src/main/java/com/getcapacitor/Bridge.java +1627 -0
  12. package/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java +212 -0
  13. package/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java +467 -0
  14. package/capacitor/src/main/java/com/getcapacitor/BridgeWebViewClient.java +105 -0
  15. package/capacitor/src/main/java/com/getcapacitor/CapConfig.java +709 -0
  16. package/capacitor/src/main/java/com/getcapacitor/CapacitorWebView.java +57 -0
  17. package/capacitor/src/main/java/com/getcapacitor/FileUtils.java +303 -0
  18. package/capacitor/src/main/java/com/getcapacitor/InvalidPluginException.java +8 -0
  19. package/capacitor/src/main/java/com/getcapacitor/InvalidPluginMethodException.java +16 -0
  20. package/capacitor/src/main/java/com/getcapacitor/JSArray.java +51 -0
  21. package/capacitor/src/main/java/com/getcapacitor/JSExport.java +208 -0
  22. package/capacitor/src/main/java/com/getcapacitor/JSExportException.java +16 -0
  23. package/capacitor/src/main/java/com/getcapacitor/JSInjector.java +127 -0
  24. package/capacitor/src/main/java/com/getcapacitor/JSObject.java +164 -0
  25. package/capacitor/src/main/java/com/getcapacitor/JSValue.java +65 -0
  26. package/capacitor/src/main/java/com/getcapacitor/Logger.java +103 -0
  27. package/capacitor/src/main/java/com/getcapacitor/MessageHandler.java +157 -0
  28. package/capacitor/src/main/java/com/getcapacitor/NativePlugin.java +37 -0
  29. package/capacitor/src/main/java/com/getcapacitor/PermissionState.java +31 -0
  30. package/capacitor/src/main/java/com/getcapacitor/Plugin.java +1050 -0
  31. package/capacitor/src/main/java/com/getcapacitor/PluginCall.java +394 -0
  32. package/capacitor/src/main/java/com/getcapacitor/PluginConfig.java +116 -0
  33. package/capacitor/src/main/java/com/getcapacitor/PluginHandle.java +160 -0
  34. package/capacitor/src/main/java/com/getcapacitor/PluginInvocationException.java +16 -0
  35. package/capacitor/src/main/java/com/getcapacitor/PluginLoadException.java +19 -0
  36. package/capacitor/src/main/java/com/getcapacitor/PluginManager.java +56 -0
  37. package/capacitor/src/main/java/com/getcapacitor/PluginMethod.java +15 -0
  38. package/capacitor/src/main/java/com/getcapacitor/PluginMethodHandle.java +33 -0
  39. package/capacitor/src/main/java/com/getcapacitor/PluginResult.java +84 -0
  40. package/capacitor/src/main/java/com/getcapacitor/ProcessedRoute.java +37 -0
  41. package/capacitor/src/main/java/com/getcapacitor/RouteProcessor.java +8 -0
  42. package/capacitor/src/main/java/com/getcapacitor/ServerPath.java +25 -0
  43. package/capacitor/src/main/java/com/getcapacitor/UriMatcher.java +184 -0
  44. package/capacitor/src/main/java/com/getcapacitor/WebViewListener.java +57 -0
  45. package/capacitor/src/main/java/com/getcapacitor/WebViewLocalServer.java +758 -0
  46. package/capacitor/src/main/java/com/getcapacitor/annotation/ActivityCallback.java +10 -0
  47. package/capacitor/src/main/java/com/getcapacitor/annotation/CapacitorPlugin.java +35 -0
  48. package/capacitor/src/main/java/com/getcapacitor/annotation/Permission.java +22 -0
  49. package/capacitor/src/main/java/com/getcapacitor/annotation/PermissionCallback.java +10 -0
  50. package/capacitor/src/main/java/com/getcapacitor/cordova/CapacitorCordovaCookieManager.java +42 -0
  51. package/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaInterfaceImpl.java +39 -0
  52. package/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java +282 -0
  53. package/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookieManager.java +239 -0
  54. package/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorCookies.java +119 -0
  55. package/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java +119 -0
  56. package/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +220 -0
  57. package/capacitor/src/main/java/com/getcapacitor/plugin/WebView.java +48 -0
  58. package/capacitor/src/main/java/com/getcapacitor/plugin/util/AssetUtil.java +358 -0
  59. package/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java +473 -0
  60. package/capacitor/src/main/java/com/getcapacitor/plugin/util/HttpRequestHandler.java +465 -0
  61. package/capacitor/src/main/java/com/getcapacitor/plugin/util/ICapacitorHttpUrlConnection.java +15 -0
  62. package/capacitor/src/main/java/com/getcapacitor/plugin/util/MimeType.java +17 -0
  63. package/capacitor/src/main/java/com/getcapacitor/util/HostMask.java +123 -0
  64. package/capacitor/src/main/java/com/getcapacitor/util/InternalUtils.java +27 -0
  65. package/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java +166 -0
  66. package/capacitor/src/main/java/com/getcapacitor/util/PermissionHelper.java +114 -0
  67. package/capacitor/src/main/java/com/getcapacitor/util/WebColor.java +28 -0
  68. package/capacitor/src/main/res/layout/capacitor_bridge_layout_main.xml +15 -0
  69. package/capacitor/src/main/res/layout/no_webview.xml +14 -0
  70. package/capacitor/src/main/res/values/colors.xml +6 -0
  71. package/capacitor/src/main/res/values/strings.xml +3 -0
  72. package/capacitor/src/main/res/values/styles.xml +6 -0
  73. package/package.json +31 -0
@@ -0,0 +1,212 @@
1
+ package com.getcapacitor;
2
+
3
+ import android.content.Intent;
4
+ import android.content.res.Configuration;
5
+ import android.os.Bundle;
6
+ import androidx.appcompat.app.AppCompatActivity;
7
+ import com.getcapacitor.android.R;
8
+ import java.util.ArrayList;
9
+ import java.util.List;
10
+
11
+ public class BridgeActivity extends AppCompatActivity {
12
+
13
+ protected Bridge bridge;
14
+ protected boolean keepRunning = true;
15
+ protected CapConfig config;
16
+
17
+ protected int activityDepth = 0;
18
+ protected List<Class<? extends Plugin>> initialPlugins = new ArrayList<>();
19
+ protected final Bridge.Builder bridgeBuilder = new Bridge.Builder(this);
20
+
21
+ @Override
22
+ protected void onCreate(Bundle savedInstanceState) {
23
+ super.onCreate(savedInstanceState);
24
+ bridgeBuilder.setInstanceState(savedInstanceState);
25
+ getApplication().setTheme(R.style.AppTheme_NoActionBar);
26
+ setTheme(R.style.AppTheme_NoActionBar);
27
+ try {
28
+ setContentView(R.layout.capacitor_bridge_layout_main);
29
+ } catch (Exception ex) {
30
+ setContentView(R.layout.no_webview);
31
+ return;
32
+ }
33
+
34
+ PluginManager loader = new PluginManager(getAssets());
35
+
36
+ try {
37
+ bridgeBuilder.addPlugins(loader.loadPluginClasses());
38
+ } catch (PluginLoadException ex) {
39
+ Logger.error("Error loading plugins.", ex);
40
+ }
41
+
42
+ this.load();
43
+ }
44
+
45
+ protected void load() {
46
+ Logger.debug("Starting BridgeActivity");
47
+
48
+ bridge = bridgeBuilder.addPlugins(initialPlugins).setConfig(config).create();
49
+
50
+ this.keepRunning = bridge.shouldKeepRunning();
51
+ this.onNewIntent(getIntent());
52
+ }
53
+
54
+ public void registerPlugin(Class<? extends Plugin> plugin) {
55
+ bridgeBuilder.addPlugin(plugin);
56
+ }
57
+
58
+ public void registerPlugins(List<Class<? extends Plugin>> plugins) {
59
+ bridgeBuilder.addPlugins(plugins);
60
+ }
61
+
62
+ public Bridge getBridge() {
63
+ return this.bridge;
64
+ }
65
+
66
+ @Override
67
+ public void onSaveInstanceState(Bundle outState) {
68
+ super.onSaveInstanceState(outState);
69
+ bridge.saveInstanceState(outState);
70
+ }
71
+
72
+ @Override
73
+ public void onStart() {
74
+ super.onStart();
75
+ activityDepth++;
76
+ if (this.bridge != null) {
77
+ this.bridge.onStart();
78
+ Logger.debug("App started");
79
+ }
80
+ }
81
+
82
+ @Override
83
+ public void onRestart() {
84
+ super.onRestart();
85
+ this.bridge.onRestart();
86
+ Logger.debug("App restarted");
87
+ }
88
+
89
+ @Override
90
+ public void onResume() {
91
+ super.onResume();
92
+ if (bridge != null) {
93
+ bridge.getApp().fireStatusChange(true);
94
+ this.bridge.onResume();
95
+ Logger.debug("App resumed");
96
+ }
97
+ }
98
+
99
+ @Override
100
+ public void onPause() {
101
+ super.onPause();
102
+ if (bridge != null) {
103
+ this.bridge.onPause();
104
+ Logger.debug("App paused");
105
+ }
106
+ }
107
+
108
+ @Override
109
+ public void onStop() {
110
+ super.onStop();
111
+ if (bridge != null) {
112
+ activityDepth = Math.max(0, activityDepth - 1);
113
+ if (activityDepth == 0) {
114
+ bridge.getApp().fireStatusChange(false);
115
+ }
116
+
117
+ this.bridge.onStop();
118
+ Logger.debug("App stopped");
119
+ }
120
+ }
121
+
122
+ @Override
123
+ public void onDestroy() {
124
+ super.onDestroy();
125
+ if (this.bridge != null) {
126
+ this.bridge.onDestroy();
127
+ Logger.debug("App destroyed");
128
+ }
129
+ }
130
+
131
+ @Override
132
+ public void onDetachedFromWindow() {
133
+ super.onDetachedFromWindow();
134
+ this.bridge.onDetachedFromWindow();
135
+ }
136
+
137
+ /**
138
+ * Handles permission request results.
139
+ *
140
+ * Capacitor is backwards compatible such that plugins using legacy permission request codes
141
+ * may coexist with plugins using the AndroidX Activity v1.2 permission callback flow introduced
142
+ * in Capacitor 3.0.
143
+ *
144
+ * In this method, plugins are checked first for ownership of the legacy permission request code.
145
+ * If the {@link Bridge#onRequestPermissionsResult(int, String[], int[])} method indicates it has
146
+ * handled the permission, then the permission callback will be considered complete. Otherwise,
147
+ * the permission will be handled using the AndroidX Activity flow.
148
+ *
149
+ * @param requestCode the request code associated with the permission request
150
+ * @param permissions the Android permission strings requested
151
+ * @param grantResults the status result of the permission request
152
+ */
153
+ @Override
154
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
155
+ if (this.bridge == null) {
156
+ return;
157
+ }
158
+
159
+ if (!bridge.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
160
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Handles activity results.
166
+ *
167
+ * Capacitor is backwards compatible such that plugins using legacy activity result codes
168
+ * may coexist with plugins using the AndroidX Activity v1.2 activity callback flow introduced
169
+ * in Capacitor 3.0.
170
+ *
171
+ * In this method, plugins are checked first for ownership of the legacy request code. If the
172
+ * {@link Bridge#onActivityResult(int, int, Intent)} method indicates it has handled the activity
173
+ * result, then the callback will be considered complete. Otherwise, the result will be handled
174
+ * using the AndroidX Activiy flow.
175
+ *
176
+ * @param requestCode the request code associated with the activity result
177
+ * @param resultCode the result code
178
+ * @param data any data included with the activity result
179
+ */
180
+ @Override
181
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
182
+ if (this.bridge == null) {
183
+ return;
184
+ }
185
+
186
+ if (!bridge.onActivityResult(requestCode, resultCode, data)) {
187
+ super.onActivityResult(requestCode, resultCode, data);
188
+ }
189
+ }
190
+
191
+ @Override
192
+ protected void onNewIntent(Intent intent) {
193
+ super.onNewIntent(intent);
194
+
195
+ if (this.bridge == null || intent == null) {
196
+ return;
197
+ }
198
+
199
+ this.bridge.onNewIntent(intent);
200
+ }
201
+
202
+ @Override
203
+ public void onConfigurationChanged(Configuration newConfig) {
204
+ super.onConfigurationChanged(newConfig);
205
+
206
+ if (this.bridge == null) {
207
+ return;
208
+ }
209
+
210
+ this.bridge.onConfigurationChanged(newConfig);
211
+ }
212
+ }
@@ -0,0 +1,467 @@
1
+ package com.getcapacitor;
2
+
3
+ import android.Manifest;
4
+ import android.annotation.SuppressLint;
5
+ import android.app.Activity;
6
+ import android.app.AlertDialog;
7
+ import android.content.ActivityNotFoundException;
8
+ import android.content.Intent;
9
+ import android.net.Uri;
10
+ import android.os.Build;
11
+ import android.os.Environment;
12
+ import android.provider.MediaStore;
13
+ import android.view.View;
14
+ import android.webkit.ConsoleMessage;
15
+ import android.webkit.GeolocationPermissions;
16
+ import android.webkit.JsPromptResult;
17
+ import android.webkit.JsResult;
18
+ import android.webkit.MimeTypeMap;
19
+ import android.webkit.PermissionRequest;
20
+ import android.webkit.ValueCallback;
21
+ import android.webkit.WebChromeClient;
22
+ import android.webkit.WebView;
23
+ import android.widget.EditText;
24
+ import androidx.activity.result.ActivityResult;
25
+ import androidx.activity.result.ActivityResultCallback;
26
+ import androidx.activity.result.ActivityResultLauncher;
27
+ import androidx.activity.result.contract.ActivityResultContracts;
28
+ import androidx.core.content.FileProvider;
29
+ import com.getcapacitor.util.PermissionHelper;
30
+ import java.io.File;
31
+ import java.io.IOException;
32
+ import java.text.SimpleDateFormat;
33
+ import java.util.*;
34
+
35
+ /**
36
+ * Custom WebChromeClient handler, required for showing dialogs, confirms, etc. in our
37
+ * WebView instance.
38
+ */
39
+ public class BridgeWebChromeClient extends WebChromeClient {
40
+
41
+ private interface PermissionListener {
42
+ void onPermissionSelect(Boolean isGranted);
43
+ }
44
+
45
+ private interface ActivityResultListener {
46
+ void onActivityResult(ActivityResult result);
47
+ }
48
+
49
+ private ActivityResultLauncher permissionLauncher;
50
+ private ActivityResultLauncher activityLauncher;
51
+ private PermissionListener permissionListener;
52
+ private ActivityResultListener activityListener;
53
+
54
+ private Bridge bridge;
55
+
56
+ public BridgeWebChromeClient(Bridge bridge) {
57
+ this.bridge = bridge;
58
+
59
+ ActivityResultCallback<Map<String, Boolean>> permissionCallback = (Map<String, Boolean> isGranted) -> {
60
+ if (permissionListener != null) {
61
+ boolean granted = true;
62
+ for (Map.Entry<String, Boolean> permission : isGranted.entrySet()) {
63
+ if (!permission.getValue()) granted = false;
64
+ }
65
+ permissionListener.onPermissionSelect(granted);
66
+ }
67
+ };
68
+
69
+ permissionLauncher = bridge.registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissionCallback);
70
+ activityLauncher = bridge.registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), (result) -> {
71
+ if (activityListener != null) {
72
+ activityListener.onActivityResult(result);
73
+ }
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Render web content in `view`.
79
+ *
80
+ * Both this method and {@link #onHideCustomView()} are required for
81
+ * rendering web content in full screen.
82
+ *
83
+ * @see <a href="https://developer.android.com/reference/android/webkit/WebChromeClient#onShowCustomView(android.view.View,%20android.webkit.WebChromeClient.CustomViewCallback)">onShowCustomView() docs</a>
84
+ */
85
+ @Override
86
+ public void onShowCustomView(View view, CustomViewCallback callback) {
87
+ callback.onCustomViewHidden();
88
+ super.onShowCustomView(view, callback);
89
+ }
90
+
91
+ /**
92
+ * Render web content in the original Web View again.
93
+ *
94
+ * Do not remove this method--@see #onShowCustomView(View, CustomViewCallback).
95
+ */
96
+ @Override
97
+ public void onHideCustomView() {
98
+ super.onHideCustomView();
99
+ }
100
+
101
+ @Override
102
+ public void onPermissionRequest(final PermissionRequest request) {
103
+ List<String> permissionList = new ArrayList<>();
104
+ if (Arrays.asList(request.getResources()).contains("android.webkit.resource.VIDEO_CAPTURE")) {
105
+ permissionList.add(Manifest.permission.CAMERA);
106
+ }
107
+ if (Arrays.asList(request.getResources()).contains("android.webkit.resource.AUDIO_CAPTURE")) {
108
+ permissionList.add(Manifest.permission.MODIFY_AUDIO_SETTINGS);
109
+ permissionList.add(Manifest.permission.RECORD_AUDIO);
110
+ }
111
+ if (!permissionList.isEmpty()) {
112
+ String[] permissions = permissionList.toArray(new String[0]);
113
+ permissionListener = (isGranted) -> {
114
+ if (isGranted) {
115
+ request.grant(request.getResources());
116
+ } else {
117
+ request.deny();
118
+ }
119
+ };
120
+ permissionLauncher.launch(permissions);
121
+ } else {
122
+ request.grant(request.getResources());
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Show the browser alert modal
128
+ * @param view
129
+ * @param url
130
+ * @param message
131
+ * @param result
132
+ * @return
133
+ */
134
+ @Override
135
+ public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
136
+ if (bridge.getActivity().isFinishing()) {
137
+ return true;
138
+ }
139
+
140
+ AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
141
+ builder
142
+ .setMessage(message)
143
+ .setPositiveButton("OK", (dialog, buttonIndex) -> {
144
+ dialog.dismiss();
145
+ result.confirm();
146
+ })
147
+ .setOnCancelListener((dialog) -> {
148
+ dialog.dismiss();
149
+ result.cancel();
150
+ });
151
+
152
+ AlertDialog dialog = builder.create();
153
+
154
+ dialog.show();
155
+
156
+ return true;
157
+ }
158
+
159
+ /**
160
+ * Show the browser confirm modal
161
+ * @param view
162
+ * @param url
163
+ * @param message
164
+ * @param result
165
+ * @return
166
+ */
167
+ @Override
168
+ public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
169
+ if (bridge.getActivity().isFinishing()) {
170
+ return true;
171
+ }
172
+
173
+ final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
174
+
175
+ builder
176
+ .setMessage(message)
177
+ .setPositiveButton("OK", (dialog, buttonIndex) -> {
178
+ dialog.dismiss();
179
+ result.confirm();
180
+ })
181
+ .setNegativeButton("Cancel", (dialog, buttonIndex) -> {
182
+ dialog.dismiss();
183
+ result.cancel();
184
+ })
185
+ .setOnCancelListener((dialog) -> {
186
+ dialog.dismiss();
187
+ result.cancel();
188
+ });
189
+
190
+ AlertDialog dialog = builder.create();
191
+
192
+ dialog.show();
193
+
194
+ return true;
195
+ }
196
+
197
+ /**
198
+ * Show the browser prompt modal
199
+ * @param view
200
+ * @param url
201
+ * @param message
202
+ * @param defaultValue
203
+ * @param result
204
+ * @return
205
+ */
206
+ @Override
207
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
208
+ if (bridge.getActivity().isFinishing()) {
209
+ return true;
210
+ }
211
+
212
+ final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
213
+ final EditText input = new EditText(view.getContext());
214
+
215
+ builder
216
+ .setMessage(message)
217
+ .setView(input)
218
+ .setPositiveButton("OK", (dialog, buttonIndex) -> {
219
+ dialog.dismiss();
220
+
221
+ String inputText1 = input.getText().toString().trim();
222
+ result.confirm(inputText1);
223
+ })
224
+ .setNegativeButton("Cancel", (dialog, buttonIndex) -> {
225
+ dialog.dismiss();
226
+ result.cancel();
227
+ })
228
+ .setOnCancelListener((dialog) -> {
229
+ dialog.dismiss();
230
+ result.cancel();
231
+ });
232
+
233
+ AlertDialog dialog = builder.create();
234
+
235
+ dialog.show();
236
+
237
+ return true;
238
+ }
239
+
240
+ /**
241
+ * Handle the browser geolocation permission prompt
242
+ * @param origin
243
+ * @param callback
244
+ */
245
+ @Override
246
+ public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
247
+ super.onGeolocationPermissionsShowPrompt(origin, callback);
248
+ Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin);
249
+ final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
250
+
251
+ if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) {
252
+ permissionListener = (isGranted) -> {
253
+ if (isGranted) {
254
+ callback.invoke(origin, true, false);
255
+ } else {
256
+ final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION };
257
+ if (
258
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
259
+ PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission)
260
+ ) {
261
+ callback.invoke(origin, true, false);
262
+ } else {
263
+ callback.invoke(origin, false, false);
264
+ }
265
+ }
266
+ };
267
+ permissionLauncher.launch(geoPermissions);
268
+ } else {
269
+ // permission is already granted
270
+ callback.invoke(origin, true, false);
271
+ Logger.debug("onGeolocationPermissionsShowPrompt: has required permission");
272
+ }
273
+ }
274
+
275
+ @Override
276
+ public boolean onShowFileChooser(
277
+ WebView webView,
278
+ final ValueCallback<Uri[]> filePathCallback,
279
+ final FileChooserParams fileChooserParams
280
+ ) {
281
+ List<String> acceptTypes = Arrays.asList(fileChooserParams.getAcceptTypes());
282
+ boolean captureEnabled = fileChooserParams.isCaptureEnabled();
283
+ boolean capturePhoto = captureEnabled && acceptTypes.contains("image/*");
284
+ final boolean captureVideo = captureEnabled && acceptTypes.contains("video/*");
285
+ if ((capturePhoto || captureVideo)) {
286
+ if (isMediaCaptureSupported()) {
287
+ showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo);
288
+ } else {
289
+ permissionListener = (isGranted) -> {
290
+ if (isGranted) {
291
+ showMediaCaptureOrFilePicker(filePathCallback, fileChooserParams, captureVideo);
292
+ } else {
293
+ Logger.warn(Logger.tags("FileChooser"), "Camera permission not granted");
294
+ filePathCallback.onReceiveValue(null);
295
+ }
296
+ };
297
+ final String[] camPermission = { Manifest.permission.CAMERA };
298
+ permissionLauncher.launch(camPermission);
299
+ }
300
+ } else {
301
+ showFilePicker(filePathCallback, fileChooserParams);
302
+ }
303
+
304
+ return true;
305
+ }
306
+
307
+ private boolean isMediaCaptureSupported() {
308
+ String[] permissions = { Manifest.permission.CAMERA };
309
+ return (
310
+ PermissionHelper.hasPermissions(bridge.getContext(), permissions) ||
311
+ !PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA)
312
+ );
313
+ }
314
+
315
+ private void showMediaCaptureOrFilePicker(ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams, boolean isVideo) {
316
+ boolean shown;
317
+ if (isVideo) {
318
+ shown = showVideoCapturePicker(filePathCallback);
319
+ } else {
320
+ shown = showImageCapturePicker(filePathCallback);
321
+ }
322
+ if (!shown) {
323
+ Logger.warn(Logger.tags("FileChooser"), "Media capture intent could not be launched. Falling back to default file picker.");
324
+ showFilePicker(filePathCallback, fileChooserParams);
325
+ }
326
+ }
327
+
328
+ @SuppressLint("QueryPermissionsNeeded")
329
+ private boolean showImageCapturePicker(final ValueCallback<Uri[]> filePathCallback) {
330
+ Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
331
+ if (takePictureIntent.resolveActivity(bridge.getActivity().getPackageManager()) == null) {
332
+ return false;
333
+ }
334
+
335
+ final Uri imageFileUri;
336
+ try {
337
+ imageFileUri = createImageFileUri();
338
+ } catch (Exception ex) {
339
+ Logger.error("Unable to create temporary media capture file: " + ex.getMessage());
340
+ return false;
341
+ }
342
+ takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
343
+ activityListener = (activityResult) -> {
344
+ Uri[] result = null;
345
+ if (activityResult.getResultCode() == Activity.RESULT_OK) {
346
+ result = new Uri[] { imageFileUri };
347
+ }
348
+ filePathCallback.onReceiveValue(result);
349
+ };
350
+ activityLauncher.launch(takePictureIntent);
351
+
352
+ return true;
353
+ }
354
+
355
+ @SuppressLint("QueryPermissionsNeeded")
356
+ private boolean showVideoCapturePicker(final ValueCallback<Uri[]> filePathCallback) {
357
+ Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
358
+ if (takeVideoIntent.resolveActivity(bridge.getActivity().getPackageManager()) == null) {
359
+ return false;
360
+ }
361
+
362
+ activityListener = (activityResult) -> {
363
+ Uri[] result = null;
364
+ if (activityResult.getResultCode() == Activity.RESULT_OK) {
365
+ result = new Uri[] { activityResult.getData().getData() };
366
+ }
367
+ filePathCallback.onReceiveValue(result);
368
+ };
369
+ activityLauncher.launch(takeVideoIntent);
370
+
371
+ return true;
372
+ }
373
+
374
+ private void showFilePicker(final ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
375
+ Intent intent = fileChooserParams.createIntent();
376
+ if (fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE) {
377
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
378
+ }
379
+ if (fileChooserParams.getAcceptTypes().length > 1 || intent.getType().startsWith(".")) {
380
+ String[] validTypes = getValidTypes(fileChooserParams.getAcceptTypes());
381
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, validTypes);
382
+ if (intent.getType().startsWith(".")) {
383
+ intent.setType(validTypes[0]);
384
+ }
385
+ }
386
+ try {
387
+ activityListener = (activityResult) -> {
388
+ Uri[] result;
389
+ Intent resultIntent = activityResult.getData();
390
+ if (activityResult.getResultCode() == Activity.RESULT_OK && resultIntent.getClipData() != null) {
391
+ final int numFiles = resultIntent.getClipData().getItemCount();
392
+ result = new Uri[numFiles];
393
+ for (int i = 0; i < numFiles; i++) {
394
+ result[i] = resultIntent.getClipData().getItemAt(i).getUri();
395
+ }
396
+ } else {
397
+ result = WebChromeClient.FileChooserParams.parseResult(activityResult.getResultCode(), resultIntent);
398
+ }
399
+ filePathCallback.onReceiveValue(result);
400
+ };
401
+ activityLauncher.launch(intent);
402
+ } catch (ActivityNotFoundException e) {
403
+ filePathCallback.onReceiveValue(null);
404
+ }
405
+ }
406
+
407
+ private String[] getValidTypes(String[] currentTypes) {
408
+ List<String> validTypes = new ArrayList<>();
409
+ MimeTypeMap mtm = MimeTypeMap.getSingleton();
410
+ for (String mime : currentTypes) {
411
+ if (mime.startsWith(".")) {
412
+ String extension = mime.substring(1);
413
+ String extensionMime = mtm.getMimeTypeFromExtension(extension);
414
+ if (extensionMime != null && !validTypes.contains(extensionMime)) {
415
+ validTypes.add(extensionMime);
416
+ }
417
+ } else if (!validTypes.contains(mime)) {
418
+ validTypes.add(mime);
419
+ }
420
+ }
421
+ Object[] validObj = validTypes.toArray();
422
+ return Arrays.copyOf(validObj, validObj.length, String[].class);
423
+ }
424
+
425
+ @Override
426
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
427
+ String tag = Logger.tags("Console");
428
+ if (consoleMessage.message() != null && isValidMsg(consoleMessage.message())) {
429
+ String msg = String.format(
430
+ "File: %s - Line %d - Msg: %s",
431
+ consoleMessage.sourceId(),
432
+ consoleMessage.lineNumber(),
433
+ consoleMessage.message()
434
+ );
435
+ String level = consoleMessage.messageLevel().name();
436
+ if ("ERROR".equalsIgnoreCase(level)) {
437
+ Logger.error(tag, msg, null);
438
+ } else if ("WARNING".equalsIgnoreCase(level)) {
439
+ Logger.warn(tag, msg);
440
+ } else if ("TIP".equalsIgnoreCase(level)) {
441
+ Logger.debug(tag, msg);
442
+ } else {
443
+ Logger.info(tag, msg);
444
+ }
445
+ }
446
+ return true;
447
+ }
448
+
449
+ public boolean isValidMsg(String msg) {
450
+ return !(msg.contains("%cresult %c") || (msg.contains("%cnative %c")) || msg.equalsIgnoreCase("console.groupEnd"));
451
+ }
452
+
453
+ private Uri createImageFileUri() throws IOException {
454
+ Activity activity = bridge.getActivity();
455
+ File photoFile = createImageFile(activity);
456
+ return FileProvider.getUriForFile(activity, bridge.getContext().getPackageName() + ".fileprovider", photoFile);
457
+ }
458
+
459
+ private File createImageFile(Activity activity) throws IOException {
460
+ // Create an image file name
461
+ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
462
+ String imageFileName = "JPEG_" + timeStamp + "_";
463
+ File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
464
+
465
+ return File.createTempFile(imageFileName, ".jpg", storageDir);
466
+ }
467
+ }