@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,1050 @@
1
+ package com.getcapacitor;
2
+
3
+ import android.app.Activity;
4
+ import android.content.Context;
5
+ import android.content.Intent;
6
+ import android.content.pm.PackageManager;
7
+ import android.content.res.Configuration;
8
+ import android.net.Uri;
9
+ import android.os.Bundle;
10
+ import androidx.activity.result.ActivityResult;
11
+ import androidx.activity.result.ActivityResultLauncher;
12
+ import androidx.activity.result.contract.ActivityResultContracts;
13
+ import androidx.annotation.NonNull;
14
+ import androidx.annotation.Nullable;
15
+ import androidx.appcompat.app.AppCompatActivity;
16
+ import androidx.core.app.ActivityCompat;
17
+ import com.getcapacitor.annotation.ActivityCallback;
18
+ import com.getcapacitor.annotation.CapacitorPlugin;
19
+ import com.getcapacitor.annotation.Permission;
20
+ import com.getcapacitor.annotation.PermissionCallback;
21
+ import com.getcapacitor.util.PermissionHelper;
22
+ import java.lang.reflect.InvocationTargetException;
23
+ import java.lang.reflect.Method;
24
+ import java.util.ArrayList;
25
+ import java.util.Arrays;
26
+ import java.util.HashMap;
27
+ import java.util.HashSet;
28
+ import java.util.List;
29
+ import java.util.Locale;
30
+ import java.util.Map;
31
+ import java.util.Set;
32
+ import java.util.concurrent.CopyOnWriteArrayList;
33
+ import org.json.JSONException;
34
+
35
+ /**
36
+ * Plugin is the base class for all plugins, containing a number of
37
+ * convenient features for interacting with the {@link Bridge}, managing
38
+ * plugin permissions, tracking lifecycle events, and more.
39
+ *
40
+ * You should inherit from this class when creating new plugins, along with
41
+ * adding the {@link CapacitorPlugin} annotation to add additional required
42
+ * metadata about the Plugin
43
+ */
44
+ public class Plugin {
45
+
46
+ // The key we will use inside of a persisted Bundle for the JSON blob
47
+ // for a plugin call options.
48
+ private static final String BUNDLE_PERSISTED_OPTIONS_JSON_KEY = "_json";
49
+
50
+ // Reference to the Bridge
51
+ protected Bridge bridge;
52
+
53
+ // Reference to the PluginHandle wrapper for this Plugin
54
+ protected PluginHandle handle;
55
+
56
+ /**
57
+ * A way for plugins to quickly save a call that they will need to reference
58
+ * between activity/permissions starts/requests
59
+ *
60
+ * @deprecated store calls on the bridge using the methods
61
+ * {@link com.getcapacitor.Bridge#saveCall(PluginCall)},
62
+ * {@link com.getcapacitor.Bridge#getSavedCall(String)} and
63
+ * {@link com.getcapacitor.Bridge#releaseCall(PluginCall)}
64
+ */
65
+ @Deprecated
66
+ protected PluginCall savedLastCall;
67
+
68
+ // Stored event listeners
69
+ private final Map<String, List<PluginCall>> eventListeners;
70
+
71
+ /**
72
+ * Launchers used by the plugin to handle activity results
73
+ */
74
+ private final Map<String, ActivityResultLauncher<Intent>> activityLaunchers = new HashMap<>();
75
+
76
+ /**
77
+ * Launchers used by the plugin to handle permission results
78
+ */
79
+ private final Map<String, ActivityResultLauncher<String[]>> permissionLaunchers = new HashMap<>();
80
+
81
+ private String lastPluginCallId;
82
+
83
+ // Stored results of an event if an event was fired and
84
+ // no listeners were attached yet. Only stores the last value.
85
+ private final Map<String, List<JSObject>> retainedEventArguments;
86
+
87
+ public Plugin() {
88
+ eventListeners = new HashMap<>();
89
+ retainedEventArguments = new HashMap<>();
90
+ }
91
+
92
+ /**
93
+ * Called when the plugin has been connected to the bridge
94
+ * and is ready to start initializing.
95
+ */
96
+ public void load() {}
97
+
98
+ /**
99
+ * Registers activity result launchers defined on plugins, used for permission requests and
100
+ * activities started for result.
101
+ */
102
+ void initializeActivityLaunchers() {
103
+ List<Method> pluginClassMethods = new ArrayList<>();
104
+ for (
105
+ Class<?> pluginCursor = getClass();
106
+ !pluginCursor.getName().equals(Object.class.getName());
107
+ pluginCursor = pluginCursor.getSuperclass()
108
+ ) {
109
+ pluginClassMethods.addAll(Arrays.asList(pluginCursor.getDeclaredMethods()));
110
+ }
111
+
112
+ for (final Method method : pluginClassMethods) {
113
+ if (method.isAnnotationPresent(ActivityCallback.class)) {
114
+ // register callbacks annotated with ActivityCallback for activity results
115
+ ActivityResultLauncher<Intent> launcher = bridge.registerForActivityResult(
116
+ new ActivityResultContracts.StartActivityForResult(),
117
+ (result) -> triggerActivityCallback(method, result)
118
+ );
119
+
120
+ activityLaunchers.put(method.getName(), launcher);
121
+ } else if (method.isAnnotationPresent(PermissionCallback.class)) {
122
+ // register callbacks annotated with PermissionCallback for permission results
123
+ ActivityResultLauncher<String[]> launcher = bridge.registerForActivityResult(
124
+ new ActivityResultContracts.RequestMultiplePermissions(),
125
+ (permissions) -> triggerPermissionCallback(method, permissions)
126
+ );
127
+
128
+ permissionLaunchers.put(method.getName(), launcher);
129
+ }
130
+ }
131
+ }
132
+
133
+ private void triggerPermissionCallback(Method method, Map<String, Boolean> permissionResultMap) {
134
+ PluginCall savedCall = bridge.getPermissionCall(handle.getId());
135
+
136
+ // validate permissions and invoke the permission result callback
137
+ if (bridge.validatePermissions(this, savedCall, permissionResultMap)) {
138
+ try {
139
+ method.setAccessible(true);
140
+ method.invoke(this, savedCall);
141
+ } catch (IllegalAccessException | InvocationTargetException e) {
142
+ e.printStackTrace();
143
+ }
144
+ }
145
+ }
146
+
147
+ private void triggerActivityCallback(Method method, ActivityResult result) {
148
+ PluginCall savedCall = bridge.getSavedCall(lastPluginCallId);
149
+ if (savedCall == null) {
150
+ savedCall = bridge.getPluginCallForLastActivity();
151
+ }
152
+ // invoke the activity result callback
153
+ try {
154
+ method.setAccessible(true);
155
+ method.invoke(this, savedCall, result);
156
+ } catch (IllegalAccessException | InvocationTargetException e) {
157
+ e.printStackTrace();
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Start activity for result with the provided Intent and resolve with the provided callback method name.
163
+ * <p>
164
+ * If there is no registered activity callback for the method name passed in, the call will
165
+ * be rejected. Make sure a valid activity result callback method is registered using the
166
+ * {@link ActivityCallback} annotation.
167
+ *
168
+ * @param call the plugin call
169
+ * @param intent the intent used to start an activity
170
+ * @param callbackName the name of the callback to run when the launched activity is finished
171
+ * @since 3.0.0
172
+ */
173
+ public void startActivityForResult(PluginCall call, Intent intent, String callbackName) {
174
+ ActivityResultLauncher<Intent> activityResultLauncher = getActivityLauncherOrReject(call, callbackName);
175
+ if (activityResultLauncher == null) {
176
+ // return when null since call was rejected in getLauncherOrReject
177
+ return;
178
+ }
179
+ bridge.setPluginCallForLastActivity(call);
180
+ lastPluginCallId = call.getCallbackId();
181
+ bridge.saveCall(call);
182
+ activityResultLauncher.launch(intent);
183
+ }
184
+
185
+ private void permissionActivityResult(PluginCall call, String[] permissionStrings, String callbackName) {
186
+ ActivityResultLauncher<String[]> permissionResultLauncher = getPermissionLauncherOrReject(call, callbackName);
187
+ if (permissionResultLauncher == null) {
188
+ // return when null since call was rejected in getLauncherOrReject
189
+ return;
190
+ }
191
+
192
+ bridge.savePermissionCall(call);
193
+ permissionResultLauncher.launch(permissionStrings);
194
+ }
195
+
196
+ /**
197
+ * Get the main {@link Context} for the current Activity (your app)
198
+ * @return the Context for the current activity
199
+ */
200
+ public Context getContext() {
201
+ return this.bridge.getContext();
202
+ }
203
+
204
+ /**
205
+ * Get the main {@link Activity} for the app
206
+ * @return the Activity for the current app
207
+ */
208
+ public AppCompatActivity getActivity() {
209
+ return this.bridge.getActivity();
210
+ }
211
+
212
+ /**
213
+ * Set the Bridge instance for this plugin
214
+ * @param bridge
215
+ */
216
+ public void setBridge(Bridge bridge) {
217
+ this.bridge = bridge;
218
+ }
219
+
220
+ /**
221
+ * Get the Bridge instance for this plugin
222
+ */
223
+ public Bridge getBridge() {
224
+ return this.bridge;
225
+ }
226
+
227
+ /**
228
+ * Set the wrapper {@link PluginHandle} instance for this plugin that
229
+ * contains additional metadata about the Plugin instance (such
230
+ * as indexed methods for reflection, and {@link CapacitorPlugin} annotation data).
231
+ * @param pluginHandle
232
+ */
233
+ public void setPluginHandle(PluginHandle pluginHandle) {
234
+ this.handle = pluginHandle;
235
+ }
236
+
237
+ /**
238
+ * Return the wrapper {@link PluginHandle} for this plugin.
239
+ *
240
+ * This wrapper contains additional metadata about the plugin instance,
241
+ * such as indexed methods for reflection, and {@link CapacitorPlugin} annotation data).
242
+ * @return
243
+ */
244
+ public PluginHandle getPluginHandle() {
245
+ return this.handle;
246
+ }
247
+
248
+ /**
249
+ * Get the root App ID
250
+ * @return
251
+ */
252
+ public String getAppId() {
253
+ return getContext().getPackageName();
254
+ }
255
+
256
+ /**
257
+ * Called to save a {@link PluginCall} in order to reference it
258
+ * later, such as in an activity or permissions result handler
259
+ * @deprecated use {@link Bridge#saveCall(PluginCall)}
260
+ *
261
+ * @param lastCall
262
+ */
263
+ @Deprecated
264
+ public void saveCall(PluginCall lastCall) {
265
+ this.savedLastCall = lastCall;
266
+ }
267
+
268
+ /**
269
+ * Set the last saved call to null to free memory
270
+ * @deprecated use {@link PluginCall#release(Bridge)}
271
+ */
272
+ @Deprecated
273
+ public void freeSavedCall() {
274
+ this.savedLastCall.release(bridge);
275
+ this.savedLastCall = null;
276
+ }
277
+
278
+ /**
279
+ * Get the last saved call, if any
280
+ * @deprecated use {@link Bridge#getSavedCall(String)}
281
+ *
282
+ * @return
283
+ */
284
+ @Deprecated
285
+ public PluginCall getSavedCall() {
286
+ return this.savedLastCall;
287
+ }
288
+
289
+ /**
290
+ * Get the config options for this plugin.
291
+ *
292
+ * @return a config object representing the plugin config options, or an empty config
293
+ * if none exists
294
+ */
295
+ public PluginConfig getConfig() {
296
+ return bridge.getConfig().getPluginConfiguration(handle.getId());
297
+ }
298
+
299
+ /**
300
+ * Get the value for a key on the config for this plugin.
301
+ * @deprecated use {@link #getConfig()} and access config values using the methods available
302
+ * depending on the type.
303
+ *
304
+ * @param key the key for the config value
305
+ * @return some object containing the value from the config
306
+ */
307
+ @Deprecated
308
+ public Object getConfigValue(String key) {
309
+ try {
310
+ PluginConfig pluginConfig = getConfig();
311
+ return pluginConfig.getConfigJSON().get(key);
312
+ } catch (JSONException ex) {
313
+ return null;
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Check whether any of the given permissions has been defined in the AndroidManifest.xml
319
+ * @deprecated use {@link #isPermissionDeclared(String)}
320
+ *
321
+ * @param permissions
322
+ * @return
323
+ */
324
+ @Deprecated
325
+ public boolean hasDefinedPermissions(String[] permissions) {
326
+ for (String permission : permissions) {
327
+ if (!PermissionHelper.hasDefinedPermission(getContext(), permission)) {
328
+ return false;
329
+ }
330
+ }
331
+ return true;
332
+ }
333
+
334
+ /**
335
+ * Check if all annotated permissions have been defined in the AndroidManifest.xml
336
+ * @deprecated use {@link #isPermissionDeclared(String)}
337
+ *
338
+ * @return true if permissions are all defined in the Manifest
339
+ */
340
+ @Deprecated
341
+ public boolean hasDefinedRequiredPermissions() {
342
+ CapacitorPlugin annotation = handle.getPluginAnnotation();
343
+ if (annotation == null) {
344
+ // Check for legacy plugin annotation, @NativePlugin
345
+ NativePlugin legacyAnnotation = handle.getLegacyPluginAnnotation();
346
+ return hasDefinedPermissions(legacyAnnotation.permissions());
347
+ } else {
348
+ for (Permission perm : annotation.permissions()) {
349
+ for (String permString : perm.strings()) {
350
+ if (!PermissionHelper.hasDefinedPermission(getContext(), permString)) {
351
+ return false;
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ return true;
358
+ }
359
+
360
+ /**
361
+ * Checks if the given permission alias is correctly declared in AndroidManifest.xml
362
+ * @param alias a permission alias defined on the plugin
363
+ * @return true only if all permissions associated with the given alias are declared in the manifest
364
+ */
365
+ public boolean isPermissionDeclared(String alias) {
366
+ CapacitorPlugin annotation = handle.getPluginAnnotation();
367
+ if (annotation != null) {
368
+ for (Permission perm : annotation.permissions()) {
369
+ if (alias.equalsIgnoreCase(perm.alias())) {
370
+ boolean result = true;
371
+ for (String permString : perm.strings()) {
372
+ result = result && PermissionHelper.hasDefinedPermission(getContext(), permString);
373
+ }
374
+
375
+ return result;
376
+ }
377
+ }
378
+ }
379
+
380
+ Logger.error(String.format("isPermissionDeclared: No alias defined for %s " + "or missing @CapacitorPlugin annotation.", alias));
381
+ return false;
382
+ }
383
+
384
+ /**
385
+ * Check whether the given permission has been granted by the user
386
+ * @deprecated use {@link #getPermissionState(String)} and {@link #getPermissionStates()} to get
387
+ * the states of permissions defined on the Plugin in conjunction with the @CapacitorPlugin
388
+ * annotation. Use the Android API {@link ActivityCompat#checkSelfPermission(Context, String)}
389
+ * methods to check permissions with Android permission strings
390
+ *
391
+ * @param permission
392
+ * @return
393
+ */
394
+ @Deprecated
395
+ public boolean hasPermission(String permission) {
396
+ return ActivityCompat.checkSelfPermission(this.getContext(), permission) == PackageManager.PERMISSION_GRANTED;
397
+ }
398
+
399
+ /**
400
+ * If the plugin annotation specified a set of permissions, this method checks if each is
401
+ * granted
402
+ * @deprecated use {@link #getPermissionState(String)} or {@link #getPermissionStates()} to
403
+ * check whether permissions are granted or not
404
+ *
405
+ * @return
406
+ */
407
+ @Deprecated
408
+ public boolean hasRequiredPermissions() {
409
+ CapacitorPlugin annotation = handle.getPluginAnnotation();
410
+ if (annotation == null) {
411
+ // Check for legacy plugin annotation, @NativePlugin
412
+ NativePlugin legacyAnnotation = handle.getLegacyPluginAnnotation();
413
+ for (String perm : legacyAnnotation.permissions()) {
414
+ if (ActivityCompat.checkSelfPermission(this.getContext(), perm) != PackageManager.PERMISSION_GRANTED) {
415
+ return false;
416
+ }
417
+ }
418
+
419
+ return true;
420
+ }
421
+
422
+ for (Permission perm : annotation.permissions()) {
423
+ for (String permString : perm.strings()) {
424
+ if (ActivityCompat.checkSelfPermission(this.getContext(), permString) != PackageManager.PERMISSION_GRANTED) {
425
+ return false;
426
+ }
427
+ }
428
+ }
429
+
430
+ return true;
431
+ }
432
+
433
+ /**
434
+ * Request all of the specified permissions in the CapacitorPlugin annotation (if any)
435
+ *
436
+ * If there is no registered permission callback for the PluginCall passed in, the call will
437
+ * be rejected. Make sure a valid permission callback method is registered using the
438
+ * {@link PermissionCallback} annotation.
439
+ *
440
+ * @since 3.0.0
441
+ * @param call the plugin call
442
+ * @param callbackName the name of the callback to run when the permission request is complete
443
+ */
444
+ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String callbackName) {
445
+ CapacitorPlugin annotation = handle.getPluginAnnotation();
446
+ if (annotation != null) {
447
+ HashSet<String> perms = new HashSet<>();
448
+ for (Permission perm : annotation.permissions()) {
449
+ perms.addAll(Arrays.asList(perm.strings()));
450
+ }
451
+
452
+ permissionActivityResult(call, perms.toArray(new String[0]), callbackName);
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Request permissions using an alias defined on the plugin.
458
+ *
459
+ * If there is no registered permission callback for the PluginCall passed in, the call will
460
+ * be rejected. Make sure a valid permission callback method is registered using the
461
+ * {@link PermissionCallback} annotation.
462
+ *
463
+ * @param alias an alias defined on the plugin
464
+ * @param call the plugin call involved in originating the request
465
+ * @param callbackName the name of the callback to run when the permission request is complete
466
+ */
467
+ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) {
468
+ requestPermissionForAliases(new String[] { alias }, call, callbackName);
469
+ }
470
+
471
+ /**
472
+ * Request permissions using aliases defined on the plugin.
473
+ *
474
+ * If there is no registered permission callback for the PluginCall passed in, the call will
475
+ * be rejected. Make sure a valid permission callback method is registered using the
476
+ * {@link PermissionCallback} annotation.
477
+ *
478
+ * @param aliases a set of aliases defined on the plugin
479
+ * @param call the plugin call involved in originating the request
480
+ * @param callbackName the name of the callback to run when the permission request is complete
481
+ */
482
+ protected void requestPermissionForAliases(@NonNull String[] aliases, @NonNull PluginCall call, @NonNull String callbackName) {
483
+ if (aliases.length == 0) {
484
+ Logger.error("No permission alias was provided");
485
+ return;
486
+ }
487
+
488
+ String[] permissions = getPermissionStringsForAliases(aliases);
489
+
490
+ if (permissions.length > 0) {
491
+ permissionActivityResult(call, permissions, callbackName);
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Gets the Android permission strings defined on the {@link CapacitorPlugin} annotation with
497
+ * the provided aliases.
498
+ *
499
+ * @param aliases aliases for permissions defined on the plugin
500
+ * @return Android permission strings associated with the provided aliases, if exists
501
+ */
502
+ private String[] getPermissionStringsForAliases(@NonNull String[] aliases) {
503
+ CapacitorPlugin annotation = handle.getPluginAnnotation();
504
+ HashSet<String> perms = new HashSet<>();
505
+ for (Permission perm : annotation.permissions()) {
506
+ if (Arrays.asList(aliases).contains(perm.alias())) {
507
+ perms.addAll(Arrays.asList(perm.strings()));
508
+ }
509
+ }
510
+
511
+ return perms.toArray(new String[0]);
512
+ }
513
+
514
+ /**
515
+ * Gets the activity launcher associated with the calling methodName, or rejects the call if
516
+ * no registered launcher exists
517
+ *
518
+ * @param call the plugin call
519
+ * @param methodName the name of the activity callback method
520
+ * @return a launcher, or null if none found
521
+ */
522
+ private @Nullable ActivityResultLauncher<Intent> getActivityLauncherOrReject(PluginCall call, String methodName) {
523
+ ActivityResultLauncher<Intent> activityLauncher = activityLaunchers.get(methodName);
524
+
525
+ // if there is no registered launcher, reject the call with an error and return null
526
+ if (activityLauncher == null) {
527
+ String registerError =
528
+ "There is no ActivityCallback method registered for the name: %s. " +
529
+ "Please define a callback method annotated with @ActivityCallback " +
530
+ "that receives arguments: (PluginCall, ActivityResult)";
531
+ registerError = String.format(Locale.US, registerError, methodName);
532
+ Logger.error(registerError);
533
+ call.reject(registerError);
534
+ return null;
535
+ }
536
+
537
+ return activityLauncher;
538
+ }
539
+
540
+ /**
541
+ * Gets the permission launcher associated with the calling methodName, or rejects the call if
542
+ * no registered launcher exists
543
+ *
544
+ * @param call the plugin call
545
+ * @param methodName the name of the permission callback method
546
+ * @return a launcher, or null if none found
547
+ */
548
+ private @Nullable ActivityResultLauncher<String[]> getPermissionLauncherOrReject(PluginCall call, String methodName) {
549
+ ActivityResultLauncher<String[]> permissionLauncher = permissionLaunchers.get(methodName);
550
+
551
+ // if there is no registered launcher, reject the call with an error and return null
552
+ if (permissionLauncher == null) {
553
+ String registerError =
554
+ "There is no PermissionCallback method registered for the name: %s. " +
555
+ "Please define a callback method annotated with @PermissionCallback " +
556
+ "that receives arguments: (PluginCall)";
557
+ registerError = String.format(Locale.US, registerError, methodName);
558
+ Logger.error(registerError);
559
+ call.reject(registerError);
560
+ return null;
561
+ }
562
+
563
+ return permissionLauncher;
564
+ }
565
+
566
+ /**
567
+ * Request all of the specified permissions in the CapacitorPlugin annotation (if any)
568
+ *
569
+ * @deprecated use {@link #requestAllPermissions(PluginCall, String)} in conjunction with @CapacitorPlugin
570
+ */
571
+ @Deprecated
572
+ public void pluginRequestAllPermissions() {
573
+ NativePlugin legacyAnnotation = handle.getLegacyPluginAnnotation();
574
+ ActivityCompat.requestPermissions(getActivity(), legacyAnnotation.permissions(), legacyAnnotation.permissionRequestCode());
575
+ }
576
+
577
+ /**
578
+ * Helper for requesting a specific permission
579
+ *
580
+ * @param permission the permission to request
581
+ * @param requestCode the requestCode to use to associate the result with the plugin
582
+ * @deprecated use {@link #requestPermissionForAlias(String, PluginCall, String)} in conjunction with @CapacitorPlugin
583
+ */
584
+ @Deprecated
585
+ public void pluginRequestPermission(String permission, int requestCode) {
586
+ ActivityCompat.requestPermissions(getActivity(), new String[] { permission }, requestCode);
587
+ }
588
+
589
+ /**
590
+ * Helper for requesting specific permissions
591
+ * @deprecated use {@link #requestPermissionForAliases(String[], PluginCall, String)} in conjunction
592
+ * with @CapacitorPlugin
593
+ *
594
+ * @param permissions the set of permissions to request
595
+ * @param requestCode the requestCode to use to associate the result with the plugin
596
+ */
597
+ @Deprecated
598
+ public void pluginRequestPermissions(String[] permissions, int requestCode) {
599
+ ActivityCompat.requestPermissions(getActivity(), permissions, requestCode);
600
+ }
601
+
602
+ /**
603
+ * Get the permission state for the provided permission alias.
604
+ *
605
+ * @param alias the permission alias to get
606
+ * @return the state of the provided permission alias or null
607
+ */
608
+ public PermissionState getPermissionState(String alias) {
609
+ return getPermissionStates().get(alias);
610
+ }
611
+
612
+ /**
613
+ * Helper to check all permissions defined on a plugin and see the state of each.
614
+ *
615
+ * @since 3.0.0
616
+ * @return A mapping of permission aliases to the associated granted status.
617
+ */
618
+ public Map<String, PermissionState> getPermissionStates() {
619
+ return bridge.getPermissionStates(this);
620
+ }
621
+
622
+ /**
623
+ * Add a listener for the given event
624
+ * @param eventName
625
+ * @param call
626
+ */
627
+ private void addEventListener(String eventName, PluginCall call) {
628
+ List<PluginCall> listeners = eventListeners.get(eventName);
629
+ if (listeners == null || listeners.isEmpty()) {
630
+ listeners = new ArrayList<>();
631
+ eventListeners.put(eventName, listeners);
632
+
633
+ // Must add the call before sending retained arguments
634
+ listeners.add(call);
635
+
636
+ sendRetainedArgumentsForEvent(eventName);
637
+ } else {
638
+ listeners.add(call);
639
+ }
640
+ }
641
+
642
+ /**
643
+ * Remove a listener from the given event
644
+ * @param eventName
645
+ * @param call
646
+ */
647
+ private void removeEventListener(String eventName, PluginCall call) {
648
+ List<PluginCall> listeners = eventListeners.get(eventName);
649
+ if (listeners == null) {
650
+ return;
651
+ }
652
+
653
+ listeners.remove(call);
654
+ }
655
+
656
+ /**
657
+ * Notify all listeners that an event occurred
658
+ * @param eventName
659
+ * @param data
660
+ */
661
+ protected void notifyListeners(String eventName, JSObject data, boolean retainUntilConsumed) {
662
+ Logger.verbose(getLogTag(), "Notifying listeners for event " + eventName);
663
+ List<PluginCall> listeners = eventListeners.get(eventName);
664
+ if (listeners == null || listeners.isEmpty()) {
665
+ Logger.debug(getLogTag(), "No listeners found for event " + eventName);
666
+ if (retainUntilConsumed) {
667
+ List<JSObject> argList = retainedEventArguments.get(eventName);
668
+
669
+ if (argList == null) {
670
+ argList = new ArrayList<JSObject>();
671
+ }
672
+
673
+ argList.add(data);
674
+ retainedEventArguments.put(eventName, argList);
675
+ }
676
+ return;
677
+ }
678
+
679
+ CopyOnWriteArrayList<PluginCall> listenersCopy = new CopyOnWriteArrayList(listeners);
680
+ for (PluginCall call : listenersCopy) {
681
+ call.resolve(data);
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Notify all listeners that an event occurred
687
+ * This calls {@link Plugin#notifyListeners(String, JSObject, boolean)}
688
+ * with retainUntilConsumed set to false
689
+ * @param eventName
690
+ * @param data
691
+ */
692
+ protected void notifyListeners(String eventName, JSObject data) {
693
+ notifyListeners(eventName, data, false);
694
+ }
695
+
696
+ /**
697
+ * Check if there are any listeners for the given event
698
+ */
699
+ protected boolean hasListeners(String eventName) {
700
+ List<PluginCall> listeners = eventListeners.get(eventName);
701
+ if (listeners == null) {
702
+ return false;
703
+ }
704
+ return !listeners.isEmpty();
705
+ }
706
+
707
+ /**
708
+ * Send retained arguments (if any) for this event. This
709
+ * is called only when the first listener for an event is added
710
+ * @param eventName
711
+ */
712
+ private void sendRetainedArgumentsForEvent(String eventName) {
713
+ // copy retained args and null source to prevent potential race conditions
714
+ List<JSObject> retainedArgs = retainedEventArguments.get(eventName);
715
+ if (retainedArgs == null) {
716
+ return;
717
+ }
718
+
719
+ retainedEventArguments.remove(eventName);
720
+
721
+ for (JSObject retained : retainedArgs) {
722
+ notifyListeners(eventName, retained);
723
+ }
724
+ }
725
+
726
+ /**
727
+ * Exported plugin call for adding a listener to this plugin
728
+ * @param call
729
+ */
730
+ @SuppressWarnings("unused")
731
+ @PluginMethod(returnType = PluginMethod.RETURN_NONE)
732
+ public void addListener(PluginCall call) {
733
+ String eventName = call.getString("eventName");
734
+ call.setKeepAlive(true);
735
+ addEventListener(eventName, call);
736
+ }
737
+
738
+ /**
739
+ * Exported plugin call to remove a listener from this plugin
740
+ * @param call
741
+ */
742
+ @SuppressWarnings("unused")
743
+ @PluginMethod(returnType = PluginMethod.RETURN_NONE)
744
+ public void removeListener(PluginCall call) {
745
+ String eventName = call.getString("eventName");
746
+ String callbackId = call.getString("callbackId");
747
+ PluginCall savedCall = bridge.getSavedCall(callbackId);
748
+ if (savedCall != null) {
749
+ removeEventListener(eventName, savedCall);
750
+ bridge.releaseCall(savedCall);
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Exported plugin call to remove all listeners from this plugin
756
+ * @param call
757
+ */
758
+ @SuppressWarnings("unused")
759
+ @PluginMethod(returnType = PluginMethod.RETURN_PROMISE)
760
+ public void removeAllListeners(PluginCall call) {
761
+ eventListeners.clear();
762
+ call.resolve();
763
+ }
764
+
765
+ public void removeAllListeners() {
766
+ eventListeners.clear();
767
+ }
768
+
769
+ /**
770
+ * Exported plugin call for checking the granted status for each permission
771
+ * declared on the plugin. This plugin call responds with a mapping of permissions to
772
+ * the associated granted status.
773
+ *
774
+ * @since 3.0.0
775
+ */
776
+ @PluginMethod
777
+ @PermissionCallback
778
+ public void checkPermissions(PluginCall pluginCall) {
779
+ Map<String, PermissionState> permissionsResult = getPermissionStates();
780
+
781
+ if (permissionsResult.size() == 0) {
782
+ // if no permissions are defined on the plugin, resolve undefined
783
+ pluginCall.resolve();
784
+ } else {
785
+ JSObject permissionsResultJSON = new JSObject();
786
+ for (Map.Entry<String, PermissionState> entry : permissionsResult.entrySet()) {
787
+ permissionsResultJSON.put(entry.getKey(), entry.getValue());
788
+ }
789
+
790
+ pluginCall.resolve(permissionsResultJSON);
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Exported plugin call to request all permissions for this plugin.
796
+ * To manually request permissions within a plugin use:
797
+ * {@link #requestAllPermissions(PluginCall, String)}, or
798
+ * {@link #requestPermissionForAlias(String, PluginCall, String)}, or
799
+ * {@link #requestPermissionForAliases(String[], PluginCall, String)}
800
+ *
801
+ * @param call the plugin call
802
+ */
803
+ @PluginMethod
804
+ public void requestPermissions(PluginCall call) {
805
+ CapacitorPlugin annotation = handle.getPluginAnnotation();
806
+ if (annotation == null) {
807
+ handleLegacyPermission(call);
808
+ } else {
809
+ // handle permission requests for plugins defined with @CapacitorPlugin (since 3.0.0)
810
+ String[] permAliases = null;
811
+ Set<String> autoGrantPerms = new HashSet<>();
812
+
813
+ // If call was made with a list of specific permission aliases to request, save them
814
+ // to be requested
815
+ JSArray providedPerms = call.getArray("permissions");
816
+ List<String> providedPermsList = null;
817
+
818
+ if (providedPerms != null) {
819
+ try {
820
+ providedPermsList = providedPerms.toList();
821
+ } catch (JSONException ignore) {
822
+ // do nothing
823
+ }
824
+ }
825
+
826
+ // If call was made without any custom permissions, request all from plugin annotation
827
+ Set<String> aliasSet = new HashSet<>();
828
+ if (providedPermsList == null || providedPermsList.isEmpty()) {
829
+ for (Permission perm : annotation.permissions()) {
830
+ // If a permission is defined with no permission strings, separate it for auto-granting.
831
+ // Otherwise, the alias is added to the list to be requested.
832
+ if (perm.strings().length == 0 || (perm.strings().length == 1 && perm.strings()[0].isEmpty())) {
833
+ if (!perm.alias().isEmpty()) {
834
+ autoGrantPerms.add(perm.alias());
835
+ }
836
+ } else {
837
+ aliasSet.add(perm.alias());
838
+ }
839
+ }
840
+
841
+ permAliases = aliasSet.toArray(new String[0]);
842
+ } else {
843
+ for (Permission perm : annotation.permissions()) {
844
+ if (providedPermsList.contains(perm.alias())) {
845
+ aliasSet.add(perm.alias());
846
+ }
847
+ }
848
+
849
+ if (aliasSet.isEmpty()) {
850
+ call.reject("No valid permission alias was requested of this plugin.");
851
+ } else {
852
+ permAliases = aliasSet.toArray(new String[0]);
853
+ }
854
+ }
855
+
856
+ if (permAliases != null && permAliases.length > 0) {
857
+ // request permissions using provided aliases or all defined on the plugin
858
+ requestPermissionForAliases(permAliases, call, "checkPermissions");
859
+ } else if (!autoGrantPerms.isEmpty()) {
860
+ // if the plugin only has auto-grant permissions, return all as GRANTED
861
+ JSObject permissionsResults = new JSObject();
862
+
863
+ for (String perm : autoGrantPerms) {
864
+ permissionsResults.put(perm, PermissionState.GRANTED.toString());
865
+ }
866
+
867
+ call.resolve(permissionsResults);
868
+ } else {
869
+ // no permissions are defined on the plugin, resolve undefined
870
+ call.resolve();
871
+ }
872
+ }
873
+ }
874
+
875
+ @SuppressWarnings("deprecation")
876
+ private void handleLegacyPermission(PluginCall call) {
877
+ // handle permission requests for plugins defined with @NativePlugin (prior to 3.0.0)
878
+ NativePlugin legacyAnnotation = this.handle.getLegacyPluginAnnotation();
879
+ String[] perms = legacyAnnotation.permissions();
880
+ if (perms.length > 0) {
881
+ saveCall(call);
882
+ pluginRequestPermissions(perms, legacyAnnotation.permissionRequestCode());
883
+ } else {
884
+ call.resolve();
885
+ }
886
+ }
887
+
888
+ /**
889
+ * Handle request permissions result. A plugin using the deprecated {@link NativePlugin}
890
+ * should override this to handle the result, or this method will handle the result
891
+ * for our convenient requestPermissions call.
892
+ * @deprecated in favor of using callbacks in conjunction with {@link CapacitorPlugin}
893
+ *
894
+ * @param requestCode
895
+ * @param permissions
896
+ * @param grantResults
897
+ */
898
+ @Deprecated
899
+ protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
900
+ if (!hasDefinedPermissions(permissions)) {
901
+ StringBuilder builder = new StringBuilder();
902
+ builder.append("Missing the following permissions in AndroidManifest.xml:\n");
903
+ String[] missing = PermissionHelper.getUndefinedPermissions(getContext(), permissions);
904
+ for (String perm : missing) {
905
+ builder.append(perm + "\n");
906
+ }
907
+ savedLastCall.reject(builder.toString());
908
+ savedLastCall = null;
909
+ }
910
+ }
911
+
912
+ /**
913
+ * Called before the app is destroyed to give a plugin the chance to
914
+ * save the last call options for a saved plugin. By default, this
915
+ * method saves the full JSON blob of the options call. Since Bundle sizes
916
+ * may be limited, plugins that expect to be called with large data
917
+ * objects (such as a file), should override this method and selectively
918
+ * store option values in a {@link Bundle} to avoid exceeding limits.
919
+ * @return a new {@link Bundle} with fields set from the options of the last saved {@link PluginCall}
920
+ */
921
+ protected Bundle saveInstanceState() {
922
+ PluginCall savedCall = bridge.getSavedCall(lastPluginCallId);
923
+
924
+ if (savedCall == null) {
925
+ return null;
926
+ }
927
+
928
+ Bundle ret = new Bundle();
929
+ JSObject callData = savedCall.getData();
930
+
931
+ if (callData != null) {
932
+ ret.putString(BUNDLE_PERSISTED_OPTIONS_JSON_KEY, callData.toString());
933
+ }
934
+
935
+ return ret;
936
+ }
937
+
938
+ /**
939
+ * Called when the app is opened with a previously un-handled
940
+ * activity response. If the plugin that started the activity
941
+ * stored data in {@link Plugin#saveInstanceState()} then this
942
+ * method will be called to allow the plugin to restore from that.
943
+ * @param state
944
+ */
945
+ protected void restoreState(Bundle state) {}
946
+
947
+ /**
948
+ * Handle activity result, should be overridden by each plugin
949
+ *
950
+ * @deprecated provide a callback method using the {@link ActivityCallback} annotation and use
951
+ * the {@link #startActivityForResult(PluginCall, Intent, String)} method
952
+ *
953
+ * @param requestCode
954
+ * @param resultCode
955
+ * @param data
956
+ */
957
+ @Deprecated
958
+ protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {}
959
+
960
+ /**
961
+ * Handle onNewIntent
962
+ * @param intent
963
+ */
964
+ protected void handleOnNewIntent(Intent intent) {}
965
+
966
+ /**
967
+ * Handle onConfigurationChanged
968
+ * @param newConfig
969
+ */
970
+ protected void handleOnConfigurationChanged(Configuration newConfig) {}
971
+
972
+ /**
973
+ * Handle onStart
974
+ */
975
+ protected void handleOnStart() {}
976
+
977
+ /**
978
+ * Handle onRestart
979
+ */
980
+ protected void handleOnRestart() {}
981
+
982
+ /**
983
+ * Handle onResume
984
+ */
985
+ protected void handleOnResume() {}
986
+
987
+ /**
988
+ * Handle onPause
989
+ */
990
+ protected void handleOnPause() {}
991
+
992
+ /**
993
+ * Handle onStop
994
+ */
995
+ protected void handleOnStop() {}
996
+
997
+ /**
998
+ * Handle onDestroy
999
+ */
1000
+ protected void handleOnDestroy() {}
1001
+
1002
+ /**
1003
+ * Give the plugins a chance to take control when a URL is about to be loaded in the WebView.
1004
+ * Returning true causes the WebView to abort loading the URL.
1005
+ * Returning false causes the WebView to continue loading the URL.
1006
+ * Returning null will defer to the default Capacitor policy
1007
+ */
1008
+ @SuppressWarnings("unused")
1009
+ public Boolean shouldOverrideLoad(Uri url) {
1010
+ return null;
1011
+ }
1012
+
1013
+ /**
1014
+ * Start a new Activity.
1015
+ *
1016
+ * Note: This method must be used by all plugins instead of calling
1017
+ * {@link Activity#startActivityForResult} as it associates the plugin with
1018
+ * any resulting data from the new Activity even if this app
1019
+ * is destroyed by the OS (to free up memory, for example).
1020
+ * @param intent
1021
+ * @param resultCode
1022
+ */
1023
+ @Deprecated
1024
+ protected void startActivityForResult(PluginCall call, Intent intent, int resultCode) {
1025
+ bridge.startActivityForPluginWithResult(call, intent, resultCode);
1026
+ }
1027
+
1028
+ /**
1029
+ * Execute the given runnable on the Bridge's task handler
1030
+ * @param runnable
1031
+ */
1032
+ public void execute(Runnable runnable) {
1033
+ bridge.execute(runnable);
1034
+ }
1035
+
1036
+ /**
1037
+ * Shortcut for getting the plugin log tag
1038
+ * @param subTags
1039
+ */
1040
+ protected String getLogTag(String... subTags) {
1041
+ return Logger.tags(subTags);
1042
+ }
1043
+
1044
+ /**
1045
+ * Gets a plugin log tag with the child's class name as subTag.
1046
+ */
1047
+ protected String getLogTag() {
1048
+ return Logger.tags(this.getClass().getSimpleName());
1049
+ }
1050
+ }