@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,1627 @@
1
+ package com.getcapacitor;
2
+
3
+ import android.annotation.SuppressLint;
4
+ import android.app.Activity;
5
+ import android.content.ActivityNotFoundException;
6
+ import android.content.Context;
7
+ import android.content.Intent;
8
+ import android.content.SharedPreferences;
9
+ import android.content.pm.ApplicationInfo;
10
+ import android.content.pm.PackageInfo;
11
+ import android.content.pm.PackageManager;
12
+ import android.content.res.Configuration;
13
+ import android.net.Uri;
14
+ import android.os.Build;
15
+ import android.os.Bundle;
16
+ import android.os.Handler;
17
+ import android.os.HandlerThread;
18
+ import android.webkit.ServiceWorkerClient;
19
+ import android.webkit.ServiceWorkerController;
20
+ import android.webkit.ValueCallback;
21
+ import android.webkit.WebResourceRequest;
22
+ import android.webkit.WebResourceResponse;
23
+ import android.webkit.WebSettings;
24
+ import android.webkit.WebView;
25
+ import androidx.activity.result.ActivityResultCallback;
26
+ import androidx.activity.result.ActivityResultLauncher;
27
+ import androidx.activity.result.contract.ActivityResultContract;
28
+ import androidx.annotation.NonNull;
29
+ import androidx.appcompat.app.AppCompatActivity;
30
+ import androidx.core.app.ActivityCompat;
31
+ import androidx.core.content.pm.PackageInfoCompat;
32
+ import androidx.fragment.app.Fragment;
33
+ import androidx.webkit.WebViewCompat;
34
+ import androidx.webkit.WebViewFeature;
35
+ import com.getcapacitor.android.R;
36
+ import com.getcapacitor.annotation.CapacitorPlugin;
37
+ import com.getcapacitor.annotation.Permission;
38
+ import com.getcapacitor.cordova.MockCordovaInterfaceImpl;
39
+ import com.getcapacitor.cordova.MockCordovaWebViewImpl;
40
+ import com.getcapacitor.util.HostMask;
41
+ import com.getcapacitor.util.InternalUtils;
42
+ import com.getcapacitor.util.PermissionHelper;
43
+ import com.getcapacitor.util.WebColor;
44
+ import java.io.File;
45
+ import java.net.SocketTimeoutException;
46
+ import java.net.URL;
47
+ import java.util.ArrayList;
48
+ import java.util.Arrays;
49
+ import java.util.Collections;
50
+ import java.util.HashMap;
51
+ import java.util.HashSet;
52
+ import java.util.LinkedList;
53
+ import java.util.List;
54
+ import java.util.Map;
55
+ import java.util.Set;
56
+ import java.util.regex.Matcher;
57
+ import java.util.regex.Pattern;
58
+ import org.apache.cordova.ConfigXmlParser;
59
+ import org.apache.cordova.CordovaPreferences;
60
+ import org.apache.cordova.CordovaWebView;
61
+ import org.apache.cordova.PluginEntry;
62
+ import org.apache.cordova.PluginManager;
63
+ import org.json.JSONException;
64
+
65
+ /**
66
+ * The Bridge class is the main engine of Capacitor. It manages
67
+ * loading and communicating with all Plugins,
68
+ * proxying Native events to Plugins, executing Plugin methods,
69
+ * communicating with the WebView, and a whole lot more.
70
+ *
71
+ * Generally, you'll not use Bridge directly, instead, extend from BridgeActivity
72
+ * to get a WebView instance and proxy native events automatically.
73
+ *
74
+ * If you want to use this Bridge in an existing Android app, please
75
+ * see the source for BridgeActivity for the methods you'll need to
76
+ * pass through to Bridge:
77
+ * <a href="https://github.com/ionic-team/capacitor/blob/HEAD/android/capacitor/src/main/java/com/getcapacitor/BridgeActivity.java">
78
+ * BridgeActivity.java</a>
79
+ */
80
+ public class Bridge {
81
+
82
+ private static final String PERMISSION_PREFS_NAME = "PluginPermStates";
83
+ private static final String BUNDLE_LAST_PLUGIN_ID_KEY = "capacitorLastActivityPluginId";
84
+ private static final String BUNDLE_LAST_PLUGIN_CALL_METHOD_NAME_KEY = "capacitorLastActivityPluginMethod";
85
+ private static final String BUNDLE_PLUGIN_CALL_OPTIONS_SAVED_KEY = "capacitorLastPluginCallOptions";
86
+ private static final String BUNDLE_PLUGIN_CALL_BUNDLE_KEY = "capacitorLastPluginCallBundle";
87
+ private static final String LAST_BINARY_VERSION_CODE = "lastBinaryVersionCode";
88
+ private static final String LAST_BINARY_VERSION_NAME = "lastBinaryVersionName";
89
+ private static final String MINIMUM_ANDROID_WEBVIEW_ERROR = "System WebView is not supported";
90
+
91
+ // The name of the directory we use to look for index.html and the rest of our web assets
92
+ public static final String DEFAULT_WEB_ASSET_DIR = "public";
93
+ public static final String CAPACITOR_HTTP_SCHEME = "http";
94
+ public static final String CAPACITOR_HTTPS_SCHEME = "https";
95
+ public static final String CAPACITOR_FILE_START = "/_capacitor_file_";
96
+ public static final String CAPACITOR_CONTENT_START = "/_capacitor_content_";
97
+ public static final String CAPACITOR_HTTP_INTERCEPTOR_START = "/_capacitor_http_interceptor_";
98
+
99
+ /** @deprecated CAPACITOR_HTTPS_INTERCEPTOR_START is no longer required. All proxied requests are handled via CAPACITOR_HTTP_INTERCEPTOR_START instead */
100
+ @Deprecated
101
+ public static final String CAPACITOR_HTTPS_INTERCEPTOR_START = "/_capacitor_https_interceptor_";
102
+
103
+ public static final String CAPACITOR_HTTP_INTERCEPTOR_URL_PARAM = "u";
104
+
105
+ public static final int DEFAULT_ANDROID_WEBVIEW_VERSION = 60;
106
+ public static final int MINIMUM_ANDROID_WEBVIEW_VERSION = 55;
107
+ public static final int DEFAULT_HUAWEI_WEBVIEW_VERSION = 10;
108
+ public static final int MINIMUM_HUAWEI_WEBVIEW_VERSION = 10;
109
+
110
+ // Loaded Capacitor config
111
+ private CapConfig config;
112
+
113
+ // A reference to the main activity for the app
114
+ private final AppCompatActivity context;
115
+ // A reference to the containing Fragment if used
116
+ private final Fragment fragment;
117
+ private WebViewLocalServer localServer;
118
+ private String localUrl;
119
+ private String appUrl;
120
+ private String appUrlConfig;
121
+ private HostMask appAllowNavigationMask;
122
+ private Set<String> allowedOriginRules = new HashSet<String>();
123
+ private ArrayList<String> authorities = new ArrayList<>();
124
+ private ArrayList<String> miscJSFileInjections = new ArrayList<String>();
125
+ private Boolean canInjectJS = true;
126
+ // A reference to the main WebView for the app
127
+ private final WebView webView;
128
+ public final MockCordovaInterfaceImpl cordovaInterface;
129
+ private CordovaWebView cordovaWebView;
130
+ private CordovaPreferences preferences;
131
+ private BridgeWebViewClient webViewClient;
132
+ private App app;
133
+
134
+ // Our MessageHandler for sending and receiving data to the WebView
135
+ private final MessageHandler msgHandler;
136
+
137
+ // The ThreadHandler for executing plugin calls
138
+ private final HandlerThread handlerThread = new HandlerThread("CapacitorPlugins");
139
+
140
+ // Our Handler for posting plugin calls. Created from the ThreadHandler
141
+ private Handler taskHandler = null;
142
+
143
+ private final List<Class<? extends Plugin>> initialPlugins;
144
+
145
+ private final List<Plugin> pluginInstances;
146
+
147
+ // A map of Plugin Id's to PluginHandle's
148
+ private Map<String, PluginHandle> plugins = new HashMap<>();
149
+
150
+ // Stored plugin calls that we're keeping around to call again someday
151
+ private Map<String, PluginCall> savedCalls = new HashMap<>();
152
+
153
+ // The call IDs of saved plugin calls with associated plugin id for handling permissions
154
+ private Map<String, LinkedList<String>> savedPermissionCallIds = new HashMap<>();
155
+
156
+ // Store a plugin that started a new activity, in case we need to resume
157
+ // the app and return that data back
158
+ private PluginCall pluginCallForLastActivity;
159
+
160
+ // Any URI that was passed to the app on start
161
+ private Uri intentUri;
162
+
163
+ // A list of listeners that trigger when webView events occur
164
+ private List<WebViewListener> webViewListeners = new ArrayList<>();
165
+
166
+ // An interface to manipulate route resolving
167
+ private RouteProcessor routeProcessor;
168
+
169
+ // A pre-determined path to load the bridge
170
+ private ServerPath serverPath;
171
+
172
+ /**
173
+ * Create the Bridge with a reference to the main {@link Activity} for the
174
+ * app, and a reference to the {@link WebView} our app will use.
175
+ * @param context
176
+ * @param webView
177
+ * @deprecated Use {@link Bridge.Builder} to create Bridge instances
178
+ */
179
+ @Deprecated
180
+ public Bridge(
181
+ AppCompatActivity context,
182
+ WebView webView,
183
+ List<Class<? extends Plugin>> initialPlugins,
184
+ MockCordovaInterfaceImpl cordovaInterface,
185
+ PluginManager pluginManager,
186
+ CordovaPreferences preferences,
187
+ CapConfig config
188
+ ) {
189
+ this(context, null, null, webView, initialPlugins, new ArrayList<>(), cordovaInterface, pluginManager, preferences, config);
190
+ }
191
+
192
+ private Bridge(
193
+ AppCompatActivity context,
194
+ ServerPath serverPath,
195
+ Fragment fragment,
196
+ WebView webView,
197
+ List<Class<? extends Plugin>> initialPlugins,
198
+ List<Plugin> pluginInstances,
199
+ MockCordovaInterfaceImpl cordovaInterface,
200
+ PluginManager pluginManager,
201
+ CordovaPreferences preferences,
202
+ CapConfig config
203
+ ) {
204
+ this.app = new App();
205
+ this.serverPath = serverPath;
206
+ this.context = context;
207
+ this.fragment = fragment;
208
+ this.webView = webView;
209
+ this.webViewClient = new BridgeWebViewClient(this);
210
+ this.initialPlugins = initialPlugins;
211
+ this.pluginInstances = pluginInstances;
212
+ this.cordovaInterface = cordovaInterface;
213
+ this.preferences = preferences;
214
+
215
+ // Start our plugin execution threads and handlers
216
+ handlerThread.start();
217
+ taskHandler = new Handler(handlerThread.getLooper());
218
+
219
+ this.config = config != null ? config : CapConfig.loadDefault(getActivity());
220
+ Logger.init(this.config);
221
+
222
+ // Initialize web view and message handler for it
223
+ this.initWebView();
224
+ this.setAllowedOriginRules();
225
+ this.msgHandler = new MessageHandler(this, webView, pluginManager);
226
+
227
+ // Grab any intent info that our app was launched with
228
+ Intent intent = context.getIntent();
229
+ this.intentUri = intent.getData();
230
+ // Register our core plugins
231
+ this.registerAllPlugins();
232
+
233
+ this.loadWebView();
234
+ }
235
+
236
+ private void setAllowedOriginRules() {
237
+ String[] appAllowNavigationConfig = this.config.getAllowNavigation();
238
+ String authority = this.getHost();
239
+ String scheme = this.getScheme();
240
+ allowedOriginRules.add(scheme + "://" + authority);
241
+ if (this.getServerUrl() != null) {
242
+ allowedOriginRules.add(this.getServerUrl());
243
+ }
244
+ if (appAllowNavigationConfig != null) {
245
+ for (String allowNavigation : appAllowNavigationConfig) {
246
+ if (!allowNavigation.startsWith("http")) {
247
+ allowedOriginRules.add("https://" + allowNavigation);
248
+ } else {
249
+ allowedOriginRules.add(allowNavigation);
250
+ }
251
+ }
252
+ authorities.addAll(Arrays.asList(appAllowNavigationConfig));
253
+ }
254
+ this.appAllowNavigationMask = HostMask.Parser.parse(appAllowNavigationConfig);
255
+ }
256
+
257
+ public App getApp() {
258
+ return app;
259
+ }
260
+
261
+ private void loadWebView() {
262
+ final boolean html5mode = this.config.isHTML5Mode();
263
+
264
+ // Start the local web server
265
+ JSInjector injector = getJSInjector();
266
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
267
+ String allowedOrigin = Uri.parse(appUrl).buildUpon().path(null).fragment(null).clearQuery().build().toString();
268
+ try {
269
+ WebViewCompat.addDocumentStartJavaScript(webView, injector.getScriptString(), Collections.singleton(allowedOrigin));
270
+ injector = null;
271
+ } catch (IllegalArgumentException ex) {
272
+ Logger.warn("Invalid url, using fallback");
273
+ }
274
+ }
275
+ localServer = new WebViewLocalServer(context, this, injector, authorities, html5mode);
276
+ localServer.hostAssets(DEFAULT_WEB_ASSET_DIR);
277
+
278
+ Logger.debug("Loading app at " + appUrl);
279
+
280
+ webView.setWebChromeClient(new BridgeWebChromeClient(this));
281
+ webView.setWebViewClient(this.webViewClient);
282
+
283
+ if (config.isResolveServiceWorkerRequests()) {
284
+ ServiceWorkerController swController = ServiceWorkerController.getInstance();
285
+ swController.setServiceWorkerClient(
286
+ new ServiceWorkerClient() {
287
+ @Override
288
+ public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
289
+ return getLocalServer().shouldInterceptRequest(request);
290
+ }
291
+ }
292
+ );
293
+ }
294
+
295
+ if (!isDeployDisabled() && !isNewBinary()) {
296
+ SharedPreferences prefs = getContext().getSharedPreferences(
297
+ com.getcapacitor.plugin.WebView.WEBVIEW_PREFS_NAME,
298
+ Activity.MODE_PRIVATE
299
+ );
300
+ String path = prefs.getString(com.getcapacitor.plugin.WebView.CAP_SERVER_PATH, null);
301
+ if (path != null && !path.isEmpty() && new File(path).exists()) {
302
+ setServerBasePath(path);
303
+ }
304
+ }
305
+ if (!this.isMinimumWebViewInstalled()) {
306
+ String errorUrl = this.getErrorUrl();
307
+ if (errorUrl != null) {
308
+ webView.loadUrl(errorUrl);
309
+ return;
310
+ } else {
311
+ Logger.error(MINIMUM_ANDROID_WEBVIEW_ERROR);
312
+ }
313
+ }
314
+
315
+ // If serverPath configured, start server based on provided path
316
+ if (serverPath != null) {
317
+ if (serverPath.getType() == ServerPath.PathType.ASSET_PATH) {
318
+ setServerAssetPath(serverPath.getPath());
319
+ } else {
320
+ setServerBasePath(serverPath.getPath());
321
+ }
322
+ } else {
323
+ // Get to work
324
+ webView.loadUrl(appUrl);
325
+ }
326
+ }
327
+
328
+ @SuppressLint("WebViewApiAvailability")
329
+ public boolean isMinimumWebViewInstalled() {
330
+ PackageManager pm = getContext().getPackageManager();
331
+
332
+ // Check getCurrentWebViewPackage() directly if above Android 8
333
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
334
+ PackageInfo info = WebView.getCurrentWebViewPackage();
335
+ Pattern pattern = Pattern.compile("(\\d+)");
336
+ Matcher matcher = pattern.matcher(info.versionName);
337
+ if (matcher.find()) {
338
+ String majorVersionStr = matcher.group(0);
339
+ int majorVersion = Integer.parseInt(majorVersionStr);
340
+ if (info.packageName.equals("com.huawei.webview")) {
341
+ return majorVersion >= config.getMinHuaweiWebViewVersion();
342
+ }
343
+ return majorVersion >= config.getMinWebViewVersion();
344
+ } else {
345
+ return false;
346
+ }
347
+ }
348
+
349
+ // Otherwise manually check WebView versions
350
+ try {
351
+ PackageInfo info = InternalUtils.getPackageInfo(pm, "com.android.chrome");
352
+ String majorVersionStr = info.versionName.split("\\.")[0];
353
+ int majorVersion = Integer.parseInt(majorVersionStr);
354
+ return majorVersion >= config.getMinWebViewVersion();
355
+ } catch (Exception ex) {
356
+ Logger.warn("Unable to get package info for 'com.google.android.webview'" + ex.toString());
357
+ }
358
+
359
+ try {
360
+ PackageInfo info = InternalUtils.getPackageInfo(pm, "com.android.webview");
361
+ String majorVersionStr = info.versionName.split("\\.")[0];
362
+ int majorVersion = Integer.parseInt(majorVersionStr);
363
+ return majorVersion >= config.getMinWebViewVersion();
364
+ } catch (Exception ex) {
365
+ Logger.warn("Unable to get package info for 'com.android.webview'" + ex.toString());
366
+ }
367
+
368
+ final int amazonFireMajorWebViewVersion = extractWebViewMajorVersion(pm, "com.amazon.webview.chromium");
369
+ if (amazonFireMajorWebViewVersion >= config.getMinWebViewVersion()) {
370
+ return true;
371
+ }
372
+
373
+ // Could not detect any webview, return false
374
+ return false;
375
+ }
376
+
377
+ private int extractWebViewMajorVersion(final PackageManager pm, final String webViewPackageName) {
378
+ try {
379
+ final PackageInfo info = InternalUtils.getPackageInfo(pm, webViewPackageName);
380
+ final String majorVersionStr = info.versionName.split("\\.")[0];
381
+ final int majorVersion = Integer.parseInt(majorVersionStr);
382
+ return majorVersion;
383
+ } catch (Exception ex) {
384
+ Logger.warn(String.format("Unable to get package info for '%s' with err '%s'", webViewPackageName, ex));
385
+ }
386
+ return 0;
387
+ }
388
+
389
+ public boolean launchIntent(Uri url) {
390
+ /*
391
+ * Give plugins the chance to handle the url
392
+ */
393
+ for (Map.Entry<String, PluginHandle> entry : plugins.entrySet()) {
394
+ Plugin plugin = entry.getValue().getInstance();
395
+ if (plugin != null) {
396
+ Boolean shouldOverrideLoad = plugin.shouldOverrideLoad(url);
397
+ if (shouldOverrideLoad != null) {
398
+ return shouldOverrideLoad;
399
+ }
400
+ }
401
+ }
402
+
403
+ if (url.getScheme().equals("data") || url.getScheme().equals("blob")) {
404
+ return false;
405
+ }
406
+
407
+ Uri appUri = Uri.parse(appUrl);
408
+ if (
409
+ !(appUri.getHost().equals(url.getHost()) && url.getScheme().equals(appUri.getScheme())) &&
410
+ !appAllowNavigationMask.matches(url.getHost())
411
+ ) {
412
+ try {
413
+ Intent openIntent = new Intent(Intent.ACTION_VIEW, url);
414
+ getContext().startActivity(openIntent);
415
+ } catch (ActivityNotFoundException e) {
416
+ // TODO - trigger an event
417
+ }
418
+ return true;
419
+ }
420
+ return false;
421
+ }
422
+
423
+ private boolean isNewBinary() {
424
+ String versionCode = "";
425
+ String versionName = "";
426
+ SharedPreferences prefs = getContext().getSharedPreferences(
427
+ com.getcapacitor.plugin.WebView.WEBVIEW_PREFS_NAME,
428
+ Activity.MODE_PRIVATE
429
+ );
430
+ String lastVersionCode = prefs.getString(LAST_BINARY_VERSION_CODE, null);
431
+ String lastVersionName = prefs.getString(LAST_BINARY_VERSION_NAME, null);
432
+
433
+ try {
434
+ PackageManager pm = getContext().getPackageManager();
435
+ PackageInfo pInfo = InternalUtils.getPackageInfo(pm, getContext().getPackageName());
436
+ versionCode = Integer.toString((int) PackageInfoCompat.getLongVersionCode(pInfo));
437
+ versionName = pInfo.versionName;
438
+ } catch (Exception ex) {
439
+ Logger.error("Unable to get package info", ex);
440
+ }
441
+
442
+ if (!versionCode.equals(lastVersionCode) || !versionName.equals(lastVersionName)) {
443
+ SharedPreferences.Editor editor = prefs.edit();
444
+ editor.putString(LAST_BINARY_VERSION_CODE, versionCode);
445
+ editor.putString(LAST_BINARY_VERSION_NAME, versionName);
446
+ editor.putString(com.getcapacitor.plugin.WebView.CAP_SERVER_PATH, "");
447
+ editor.apply();
448
+ return true;
449
+ }
450
+ return false;
451
+ }
452
+
453
+ public boolean isDeployDisabled() {
454
+ return preferences.getBoolean("DisableDeploy", false);
455
+ }
456
+
457
+ public boolean shouldKeepRunning() {
458
+ return preferences.getBoolean("KeepRunning", true);
459
+ }
460
+
461
+ public void handleAppUrlLoadError(Exception ex) {
462
+ if (ex instanceof SocketTimeoutException) {
463
+ Logger.error(
464
+ "Unable to load app. Ensure the server is running at " +
465
+ appUrl +
466
+ ", or modify the " +
467
+ "appUrl setting in capacitor.config.json (make sure to npx cap copy after to commit changes).",
468
+ ex
469
+ );
470
+ }
471
+ }
472
+
473
+ public boolean isDevMode() {
474
+ return (getActivity().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
475
+ }
476
+
477
+ protected void setCordovaWebView(CordovaWebView cordovaWebView) {
478
+ this.cordovaWebView = cordovaWebView;
479
+ }
480
+
481
+ /**
482
+ * Get the Context for the App
483
+ * @return
484
+ */
485
+ public Context getContext() {
486
+ return this.context;
487
+ }
488
+
489
+ /**
490
+ * Get the activity for the app
491
+ * @return
492
+ */
493
+ public AppCompatActivity getActivity() {
494
+ return this.context;
495
+ }
496
+
497
+ /**
498
+ * Get the fragment for the app, if applicable. This will likely be null unless Capacitor
499
+ * is being used embedded in a Native Android app.
500
+ *
501
+ * @return The fragment containing the Capacitor WebView.
502
+ */
503
+ public Fragment getFragment() {
504
+ return this.fragment;
505
+ }
506
+
507
+ /**
508
+ * Get the core WebView under Capacitor's control
509
+ * @return
510
+ */
511
+ public WebView getWebView() {
512
+ return this.webView;
513
+ }
514
+
515
+ /**
516
+ * Get the URI that was used to launch the app (if any)
517
+ * @return
518
+ */
519
+ public Uri getIntentUri() {
520
+ return intentUri;
521
+ }
522
+
523
+ /**
524
+ * Get scheme that is used to serve content
525
+ * @return
526
+ */
527
+ public String getScheme() {
528
+ return this.config.getAndroidScheme();
529
+ }
530
+
531
+ /**
532
+ * Get host name that is used to serve content
533
+ * @return
534
+ */
535
+ public String getHost() {
536
+ return this.config.getHostname();
537
+ }
538
+
539
+ /**
540
+ * Get the server url that is used to serve content
541
+ * @return
542
+ */
543
+ public String getServerUrl() {
544
+ return this.config.getServerUrl();
545
+ }
546
+
547
+ public String getErrorUrl() {
548
+ String errorPath = this.config.getErrorPath();
549
+
550
+ if (errorPath != null && !errorPath.trim().isEmpty()) {
551
+ String authority = this.getHost();
552
+ String scheme = this.getScheme();
553
+
554
+ String localUrl = scheme + "://" + authority;
555
+
556
+ return localUrl + "/" + errorPath;
557
+ }
558
+
559
+ return null;
560
+ }
561
+
562
+ public String getAppUrl() {
563
+ return appUrl;
564
+ }
565
+
566
+ public CapConfig getConfig() {
567
+ return this.config;
568
+ }
569
+
570
+ public void reset() {
571
+ savedCalls = new HashMap<>();
572
+ for (PluginHandle handle : this.plugins.values()) {
573
+ handle.getInstance().removeAllListeners();
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Initialize the WebView, setting required flags
579
+ */
580
+ @SuppressLint("SetJavaScriptEnabled")
581
+ private void initWebView() {
582
+ WebSettings settings = webView.getSettings();
583
+ settings.setJavaScriptEnabled(true);
584
+ settings.setDomStorageEnabled(true);
585
+ settings.setGeolocationEnabled(true);
586
+ settings.setMediaPlaybackRequiresUserGesture(false);
587
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
588
+ if (this.config.isMixedContentAllowed()) {
589
+ settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
590
+ }
591
+
592
+ String appendUserAgent = this.config.getAppendedUserAgentString();
593
+ if (appendUserAgent != null) {
594
+ String defaultUserAgent = settings.getUserAgentString();
595
+ settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
596
+ }
597
+ String overrideUserAgent = this.config.getOverriddenUserAgentString();
598
+ if (overrideUserAgent != null) {
599
+ settings.setUserAgentString(overrideUserAgent);
600
+ }
601
+
602
+ String backgroundColor = this.config.getBackgroundColor();
603
+ try {
604
+ if (backgroundColor != null) {
605
+ webView.setBackgroundColor(WebColor.parseColor(backgroundColor));
606
+ }
607
+ } catch (IllegalArgumentException ex) {
608
+ Logger.debug("WebView background color not applied");
609
+ }
610
+
611
+ settings.setDisplayZoomControls(false);
612
+ settings.setBuiltInZoomControls(this.config.isZoomableWebView());
613
+
614
+ if (config.isInitialFocus()) {
615
+ webView.requestFocusFromTouch();
616
+ }
617
+
618
+ WebView.setWebContentsDebuggingEnabled(this.config.isWebContentsDebuggingEnabled());
619
+
620
+ appUrlConfig = this.getServerUrl();
621
+ String authority = this.getHost();
622
+ authorities.add(authority);
623
+ String scheme = this.getScheme();
624
+
625
+ localUrl = scheme + "://" + authority;
626
+
627
+ if (appUrlConfig != null) {
628
+ try {
629
+ URL appUrlObject = new URL(appUrlConfig);
630
+ authorities.add(appUrlObject.getAuthority());
631
+ } catch (Exception ex) {
632
+ Logger.error("Provided server url is invalid: " + ex.getMessage());
633
+ return;
634
+ }
635
+ localUrl = appUrlConfig;
636
+ appUrl = appUrlConfig;
637
+ } else {
638
+ appUrl = localUrl;
639
+ // custom URL schemes requires path ending with /
640
+ if (!scheme.equals(Bridge.CAPACITOR_HTTP_SCHEME) && !scheme.equals(CAPACITOR_HTTPS_SCHEME)) {
641
+ appUrl += "/";
642
+ }
643
+ }
644
+
645
+ String appUrlPath = this.config.getStartPath();
646
+ if (appUrlPath != null && !appUrlPath.trim().isEmpty()) {
647
+ appUrl += appUrlPath;
648
+ }
649
+ }
650
+
651
+ /**
652
+ * Register our core Plugin APIs
653
+ */
654
+ private void registerAllPlugins() {
655
+ this.registerPlugin(com.getcapacitor.plugin.CapacitorCookies.class);
656
+ this.registerPlugin(com.getcapacitor.plugin.WebView.class);
657
+ this.registerPlugin(com.getcapacitor.plugin.CapacitorHttp.class);
658
+ this.registerPlugin(com.getcapacitor.plugin.SystemBars.class);
659
+
660
+ for (Class<? extends Plugin> pluginClass : this.initialPlugins) {
661
+ this.registerPlugin(pluginClass);
662
+ }
663
+
664
+ for (Plugin plugin : pluginInstances) {
665
+ registerPluginInstance(plugin);
666
+ }
667
+ }
668
+
669
+ /**
670
+ * Register additional plugins
671
+ * @param pluginClasses the plugins to register
672
+ */
673
+ public void registerPlugins(Class<? extends Plugin>[] pluginClasses) {
674
+ for (Class<? extends Plugin> plugin : pluginClasses) {
675
+ this.registerPlugin(plugin);
676
+ }
677
+ }
678
+
679
+ public void registerPluginInstances(Plugin[] pluginInstances) {
680
+ for (Plugin plugin : pluginInstances) {
681
+ this.registerPluginInstance(plugin);
682
+ }
683
+ }
684
+
685
+ @SuppressWarnings("deprecation")
686
+ private String getLegacyPluginName(Class<? extends Plugin> pluginClass) {
687
+ NativePlugin legacyPluginAnnotation = pluginClass.getAnnotation(NativePlugin.class);
688
+ if (legacyPluginAnnotation == null) {
689
+ Logger.error("Plugin doesn't have the @CapacitorPlugin annotation. Please add it");
690
+ return null;
691
+ }
692
+
693
+ return legacyPluginAnnotation.name();
694
+ }
695
+
696
+ /**
697
+ * Register a plugin class
698
+ * @param pluginClass a class inheriting from Plugin
699
+ */
700
+ public void registerPlugin(Class<? extends Plugin> pluginClass) {
701
+ String pluginId = pluginId(pluginClass);
702
+ if (pluginId == null) return;
703
+
704
+ try {
705
+ this.plugins.put(pluginId, new PluginHandle(this, pluginClass));
706
+ } catch (InvalidPluginException ex) {
707
+ logInvalidPluginException(pluginClass);
708
+ } catch (PluginLoadException ex) {
709
+ logPluginLoadException(pluginClass, ex);
710
+ }
711
+ }
712
+
713
+ public void registerPluginInstance(Plugin plugin) {
714
+ Class<? extends Plugin> clazz = plugin.getClass();
715
+ String pluginId = pluginId(clazz);
716
+ if (pluginId == null) return;
717
+
718
+ try {
719
+ this.plugins.put(pluginId, new PluginHandle(this, plugin));
720
+ } catch (InvalidPluginException ex) {
721
+ logInvalidPluginException(clazz);
722
+ }
723
+ }
724
+
725
+ private String pluginId(Class<? extends Plugin> clazz) {
726
+ String pluginName = pluginName(clazz);
727
+ String pluginId = clazz.getSimpleName();
728
+ if (pluginName == null) return null;
729
+
730
+ if (!pluginName.equals("")) {
731
+ pluginId = pluginName;
732
+ }
733
+ Logger.debug("Registering plugin instance: " + pluginId);
734
+ return pluginId;
735
+ }
736
+
737
+ private String pluginName(Class<? extends Plugin> clazz) {
738
+ String pluginName;
739
+ CapacitorPlugin pluginAnnotation = clazz.getAnnotation(CapacitorPlugin.class);
740
+ if (pluginAnnotation == null) {
741
+ pluginName = this.getLegacyPluginName(clazz);
742
+ } else {
743
+ pluginName = pluginAnnotation.name();
744
+ }
745
+
746
+ return pluginName;
747
+ }
748
+
749
+ private void logInvalidPluginException(Class<? extends Plugin> clazz) {
750
+ Logger.error(
751
+ "NativePlugin " +
752
+ clazz.getName() +
753
+ " is invalid. Ensure the @CapacitorPlugin annotation exists on the plugin class and" +
754
+ " the class extends Plugin"
755
+ );
756
+ }
757
+
758
+ private void logPluginLoadException(Class<? extends Plugin> clazz, Exception ex) {
759
+ Logger.error("NativePlugin " + clazz.getName() + " failed to load", ex);
760
+ }
761
+
762
+ public PluginHandle getPlugin(String pluginId) {
763
+ return this.plugins.get(pluginId);
764
+ }
765
+
766
+ /**
767
+ * Find the plugin handle that responds to the given request code. This will
768
+ * fire after certain Android OS intent results/permission checks/etc.
769
+ * @param requestCode
770
+ * @return
771
+ */
772
+ @Deprecated
773
+ @SuppressWarnings("deprecation")
774
+ public PluginHandle getPluginWithRequestCode(int requestCode) {
775
+ for (PluginHandle handle : this.plugins.values()) {
776
+ int[] requestCodes;
777
+
778
+ CapacitorPlugin pluginAnnotation = handle.getPluginAnnotation();
779
+ if (pluginAnnotation == null) {
780
+ // Check for legacy plugin annotation, @NativePlugin
781
+ NativePlugin legacyPluginAnnotation = handle.getLegacyPluginAnnotation();
782
+ if (legacyPluginAnnotation == null) {
783
+ continue;
784
+ }
785
+
786
+ if (legacyPluginAnnotation.permissionRequestCode() == requestCode) {
787
+ return handle;
788
+ }
789
+
790
+ requestCodes = legacyPluginAnnotation.requestCodes();
791
+
792
+ for (int rc : requestCodes) {
793
+ if (rc == requestCode) {
794
+ return handle;
795
+ }
796
+ }
797
+ } else {
798
+ requestCodes = pluginAnnotation.requestCodes();
799
+
800
+ for (int rc : requestCodes) {
801
+ if (rc == requestCode) {
802
+ return handle;
803
+ }
804
+ }
805
+ }
806
+ }
807
+ return null;
808
+ }
809
+
810
+ /**
811
+ * Call a method on a plugin.
812
+ * @param pluginId the plugin id to use to lookup the plugin handle
813
+ * @param methodName the name of the method to call
814
+ * @param call the call object to pass to the method
815
+ */
816
+ public void callPluginMethod(String pluginId, final String methodName, final PluginCall call) {
817
+ try {
818
+ final PluginHandle plugin = this.getPlugin(pluginId);
819
+
820
+ if (plugin == null) {
821
+ Logger.error("unable to find plugin : " + pluginId);
822
+ call.errorCallback("unable to find plugin : " + pluginId);
823
+ return;
824
+ }
825
+
826
+ if (Logger.shouldLog()) {
827
+ Logger.verbose(
828
+ "callback: " +
829
+ call.getCallbackId() +
830
+ ", pluginId: " +
831
+ plugin.getId() +
832
+ ", methodName: " +
833
+ methodName +
834
+ ", methodData: " +
835
+ call.getData().toString()
836
+ );
837
+ }
838
+
839
+ Runnable currentThreadTask = () -> {
840
+ try {
841
+ plugin.invoke(methodName, call);
842
+
843
+ if (call.isKeptAlive()) {
844
+ saveCall(call);
845
+ }
846
+ } catch (PluginLoadException | InvalidPluginMethodException ex) {
847
+ Logger.error("Unable to execute plugin method", ex);
848
+ } catch (Exception ex) {
849
+ Logger.error("Serious error executing plugin", ex);
850
+ throw new RuntimeException(ex);
851
+ }
852
+ };
853
+
854
+ taskHandler.post(currentThreadTask);
855
+ } catch (Exception ex) {
856
+ Logger.error(Logger.tags("callPluginMethod"), "error : " + ex, null);
857
+ call.errorCallback(ex.toString());
858
+ }
859
+ }
860
+
861
+ /**
862
+ * Evaluate JavaScript in the web view. This method
863
+ * executes on the main thread automatically.
864
+ * @param js the JS to execute
865
+ * @param callback an optional ValueCallback that will synchronously receive a value
866
+ * after calling the JS
867
+ */
868
+ public void eval(final String js, final ValueCallback<String> callback) {
869
+ Handler mainHandler = new Handler(context.getMainLooper());
870
+ mainHandler.post(() -> webView.evaluateJavascript(js, callback));
871
+ }
872
+
873
+ public void logToJs(final String message, final String level) {
874
+ eval("window.Capacitor.logJs(\"" + message + "\", \"" + level + "\")", null);
875
+ }
876
+
877
+ public void logToJs(final String message) {
878
+ logToJs(message, "log");
879
+ }
880
+
881
+ public void triggerJSEvent(final String eventName, final String target) {
882
+ eval("window.Capacitor.triggerEvent(\"" + eventName + "\", \"" + target + "\")", (s) -> {});
883
+ }
884
+
885
+ public void triggerJSEvent(final String eventName, final String target, final String data) {
886
+ eval("window.Capacitor.triggerEvent(\"" + eventName + "\", \"" + target + "\", " + data + ")", (s) -> {});
887
+ }
888
+
889
+ public void triggerWindowJSEvent(final String eventName) {
890
+ this.triggerJSEvent(eventName, "window");
891
+ }
892
+
893
+ public void triggerWindowJSEvent(final String eventName, final String data) {
894
+ this.triggerJSEvent(eventName, "window", data);
895
+ }
896
+
897
+ public void triggerDocumentJSEvent(final String eventName) {
898
+ this.triggerJSEvent(eventName, "document");
899
+ }
900
+
901
+ public void triggerDocumentJSEvent(final String eventName, final String data) {
902
+ this.triggerJSEvent(eventName, "document", data);
903
+ }
904
+
905
+ public void execute(Runnable runnable) {
906
+ taskHandler.post(runnable);
907
+ }
908
+
909
+ public void executeOnMainThread(Runnable runnable) {
910
+ Handler mainHandler = new Handler(context.getMainLooper());
911
+
912
+ mainHandler.post(runnable);
913
+ }
914
+
915
+ /**
916
+ * Retain a call between plugin invocations
917
+ * @param call
918
+ */
919
+ public void saveCall(PluginCall call) {
920
+ this.savedCalls.put(call.getCallbackId(), call);
921
+ }
922
+
923
+ /**
924
+ * Get a retained plugin call
925
+ * @param callbackId the callbackId to use to lookup the call with
926
+ * @return the stored call
927
+ */
928
+ public PluginCall getSavedCall(String callbackId) {
929
+ if (callbackId == null) {
930
+ return null;
931
+ }
932
+
933
+ return this.savedCalls.get(callbackId);
934
+ }
935
+
936
+ PluginCall getPluginCallForLastActivity() {
937
+ PluginCall pluginCallForLastActivity = this.pluginCallForLastActivity;
938
+ this.pluginCallForLastActivity = null;
939
+ return pluginCallForLastActivity;
940
+ }
941
+
942
+ void setPluginCallForLastActivity(PluginCall pluginCallForLastActivity) {
943
+ this.pluginCallForLastActivity = pluginCallForLastActivity;
944
+ }
945
+
946
+ /**
947
+ * Release a retained call
948
+ * @param call a call to release
949
+ */
950
+ public void releaseCall(PluginCall call) {
951
+ releaseCall(call.getCallbackId());
952
+ }
953
+
954
+ /**
955
+ * Release a retained call by its ID
956
+ * @param callbackId an ID of a callback to release
957
+ */
958
+ public void releaseCall(String callbackId) {
959
+ this.savedCalls.remove(callbackId);
960
+ }
961
+
962
+ /**
963
+ * Removes the earliest saved call prior to a permissions request for a given plugin and
964
+ * returns it.
965
+ *
966
+ * @return The saved plugin call
967
+ */
968
+ protected PluginCall getPermissionCall(String pluginId) {
969
+ LinkedList<String> permissionCallIds = this.savedPermissionCallIds.get(pluginId);
970
+ String savedCallId = null;
971
+ if (permissionCallIds != null) {
972
+ savedCallId = permissionCallIds.poll();
973
+ }
974
+
975
+ return getSavedCall(savedCallId);
976
+ }
977
+
978
+ /**
979
+ * Save a call to be retrieved after requesting permissions. Calls are saved in order.
980
+ *
981
+ * @param call The plugin call to save.
982
+ */
983
+ protected void savePermissionCall(PluginCall call) {
984
+ if (call != null) {
985
+ if (!savedPermissionCallIds.containsKey(call.getPluginId())) {
986
+ savedPermissionCallIds.put(call.getPluginId(), new LinkedList<>());
987
+ }
988
+
989
+ savedPermissionCallIds.get(call.getPluginId()).add(call.getCallbackId());
990
+ saveCall(call);
991
+ }
992
+ }
993
+
994
+ /**
995
+ * Register an Activity Result Launcher to the containing Fragment or Activity.
996
+ *
997
+ * @param contract A contract specifying that an activity can be called with an input of
998
+ * type I and produce an output of type O.
999
+ * @param callback The callback run on Activity Result.
1000
+ * @return A registered Activity Result Launcher.
1001
+ */
1002
+ public <I, O> ActivityResultLauncher<I> registerForActivityResult(
1003
+ @NonNull final ActivityResultContract<I, O> contract,
1004
+ @NonNull final ActivityResultCallback<O> callback
1005
+ ) {
1006
+ if (fragment != null) {
1007
+ return fragment.registerForActivityResult(contract, callback);
1008
+ } else {
1009
+ return context.registerForActivityResult(contract, callback);
1010
+ }
1011
+ }
1012
+
1013
+ /**
1014
+ * Build the JSInjector that will be used to inject JS into files served to the app,
1015
+ * to ensure that Capacitor's JS and the JS for all the plugins is loaded each time.
1016
+ */
1017
+ private JSInjector getJSInjector() {
1018
+ try {
1019
+ String globalJS = JSExport.getGlobalJS(context, config.isLoggingEnabled(), isDevMode());
1020
+ String bridgeJS = JSExport.getBridgeJS(context);
1021
+ String pluginJS = JSExport.getPluginJS(plugins.values());
1022
+ String cordovaJS = JSExport.getCordovaJS(context);
1023
+ String cordovaPluginsJS = JSExport.getCordovaPluginJS(context);
1024
+ String cordovaPluginsFileJS = JSExport.getCordovaPluginsFileJS(context);
1025
+ String localUrlJS = "window.WEBVIEW_SERVER_URL = '" + localUrl + "';";
1026
+ String miscJS = JSExport.getMiscFileJS(miscJSFileInjections, context);
1027
+
1028
+ miscJSFileInjections = new ArrayList<>();
1029
+ canInjectJS = false;
1030
+
1031
+ return new JSInjector(globalJS, bridgeJS, pluginJS, cordovaJS, cordovaPluginsJS, cordovaPluginsFileJS, localUrlJS, miscJS);
1032
+ } catch (Exception ex) {
1033
+ Logger.error("Unable to export Capacitor JS. App will not function!", ex);
1034
+ }
1035
+ return null;
1036
+ }
1037
+
1038
+ /**
1039
+ * Inject JavaScript from an external file before the WebView loads.
1040
+ * @param path relative to public folder
1041
+ */
1042
+ public void injectScriptBeforeLoad(String path) {
1043
+ if (canInjectJS) {
1044
+ miscJSFileInjections.add(path);
1045
+ }
1046
+ }
1047
+
1048
+ /**
1049
+ * Restore any saved bundle state data
1050
+ * @param savedInstanceState
1051
+ */
1052
+ public void restoreInstanceState(Bundle savedInstanceState) {
1053
+ String lastPluginId = savedInstanceState.getString(BUNDLE_LAST_PLUGIN_ID_KEY);
1054
+ String lastPluginCallMethod = savedInstanceState.getString(BUNDLE_LAST_PLUGIN_CALL_METHOD_NAME_KEY);
1055
+ String lastOptionsJson = savedInstanceState.getString(BUNDLE_PLUGIN_CALL_OPTIONS_SAVED_KEY);
1056
+
1057
+ if (lastPluginId != null) {
1058
+ // If we have JSON blob saved, create a new plugin call with the original options
1059
+ if (lastOptionsJson != null) {
1060
+ try {
1061
+ JSObject options = new JSObject(lastOptionsJson);
1062
+
1063
+ pluginCallForLastActivity = new PluginCall(
1064
+ msgHandler,
1065
+ lastPluginId,
1066
+ PluginCall.CALLBACK_ID_DANGLING,
1067
+ lastPluginCallMethod,
1068
+ options
1069
+ );
1070
+ } catch (JSONException ex) {
1071
+ Logger.error("Unable to restore plugin call, unable to parse persisted JSON object", ex);
1072
+ }
1073
+ }
1074
+
1075
+ // Let the plugin restore any state it needs
1076
+ Bundle bundleData = savedInstanceState.getBundle(BUNDLE_PLUGIN_CALL_BUNDLE_KEY);
1077
+ PluginHandle lastPlugin = getPlugin(lastPluginId);
1078
+ if (bundleData != null && lastPlugin != null) {
1079
+ lastPlugin.getInstance().restoreState(bundleData);
1080
+ } else {
1081
+ Logger.error("Unable to restore last plugin call");
1082
+ }
1083
+ }
1084
+ }
1085
+
1086
+ public void saveInstanceState(Bundle outState) {
1087
+ Logger.debug("Saving instance state!");
1088
+
1089
+ // If there was a last PluginCall for a started activity, we need to
1090
+ // persist it so we can load it again in case our app gets terminated
1091
+ if (pluginCallForLastActivity != null) {
1092
+ PluginCall call = pluginCallForLastActivity;
1093
+ PluginHandle handle = getPlugin(call.getPluginId());
1094
+
1095
+ if (handle != null) {
1096
+ Bundle bundle = handle.getInstance().saveInstanceState();
1097
+ if (bundle != null) {
1098
+ outState.putString(BUNDLE_LAST_PLUGIN_ID_KEY, call.getPluginId());
1099
+ outState.putString(BUNDLE_LAST_PLUGIN_CALL_METHOD_NAME_KEY, call.getMethodName());
1100
+ outState.putString(BUNDLE_PLUGIN_CALL_OPTIONS_SAVED_KEY, call.getData().toString());
1101
+ outState.putBundle(BUNDLE_PLUGIN_CALL_BUNDLE_KEY, bundle);
1102
+ } else {
1103
+ Logger.error("Couldn't save last " + call.getPluginId() + "'s Plugin " + call.getMethodName() + " call");
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+
1109
+ @Deprecated
1110
+ @SuppressWarnings("deprecation")
1111
+ public void startActivityForPluginWithResult(PluginCall call, Intent intent, int requestCode) {
1112
+ Logger.debug("Starting activity for result");
1113
+
1114
+ pluginCallForLastActivity = call;
1115
+
1116
+ getActivity().startActivityForResult(intent, requestCode);
1117
+ }
1118
+
1119
+ /**
1120
+ * Check for legacy Capacitor or Cordova plugins that may have registered to handle a permission
1121
+ * request, and handle them if so. If not handled, false is returned.
1122
+ *
1123
+ * @param requestCode the code that was requested
1124
+ * @param permissions the permissions requested
1125
+ * @param grantResults the set of granted/denied permissions
1126
+ * @return true if permission code was handled by a plugin explicitly, false if not
1127
+ */
1128
+ @SuppressWarnings("deprecation")
1129
+ boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
1130
+ PluginHandle plugin = getPluginWithRequestCode(requestCode);
1131
+
1132
+ if (plugin == null) {
1133
+ boolean permissionHandled = false;
1134
+ Logger.debug("Unable to find a Capacitor plugin to handle permission requestCode, trying Cordova plugins " + requestCode);
1135
+ try {
1136
+ permissionHandled = cordovaInterface.handlePermissionResult(requestCode, permissions, grantResults);
1137
+ } catch (JSONException e) {
1138
+ Logger.debug("Error on Cordova plugin permissions request " + e.getMessage());
1139
+ }
1140
+ return permissionHandled;
1141
+ }
1142
+
1143
+ // Call deprecated method if using deprecated NativePlugin annotation
1144
+ if (plugin.getPluginAnnotation() == null) {
1145
+ plugin.getInstance().handleRequestPermissionsResult(requestCode, permissions, grantResults);
1146
+ return true;
1147
+ }
1148
+
1149
+ return false;
1150
+ }
1151
+
1152
+ /**
1153
+ * Saves permission states and rejects if permissions were not correctly defined in
1154
+ * the AndroidManifest.xml file.
1155
+ *
1156
+ * @param plugin
1157
+ * @param savedCall
1158
+ * @param permissions
1159
+ * @return true if permissions were saved and defined correctly, false if not
1160
+ */
1161
+ protected boolean validatePermissions(Plugin plugin, PluginCall savedCall, Map<String, Boolean> permissions) {
1162
+ SharedPreferences prefs = getContext().getSharedPreferences(PERMISSION_PREFS_NAME, Activity.MODE_PRIVATE);
1163
+
1164
+ for (Map.Entry<String, Boolean> permission : permissions.entrySet()) {
1165
+ String permString = permission.getKey();
1166
+ boolean isGranted = permission.getValue();
1167
+
1168
+ if (isGranted) {
1169
+ // Permission granted. If previously denied, remove cached state
1170
+ String state = prefs.getString(permString, null);
1171
+
1172
+ if (state != null) {
1173
+ SharedPreferences.Editor editor = prefs.edit();
1174
+ editor.remove(permString);
1175
+ editor.apply();
1176
+ }
1177
+ } else {
1178
+ SharedPreferences.Editor editor = prefs.edit();
1179
+
1180
+ if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permString)) {
1181
+ // Permission denied, can prompt again with rationale
1182
+ editor.putString(permString, PermissionState.PROMPT_WITH_RATIONALE.toString());
1183
+ } else {
1184
+ // Permission denied permanently, store this state for future reference
1185
+ editor.putString(permString, PermissionState.DENIED.toString());
1186
+ }
1187
+
1188
+ editor.apply();
1189
+ }
1190
+ }
1191
+
1192
+ String[] permStrings = permissions.keySet().toArray(new String[0]);
1193
+
1194
+ if (!PermissionHelper.hasDefinedPermissions(getContext(), permStrings)) {
1195
+ StringBuilder builder = new StringBuilder();
1196
+ builder.append("Missing the following permissions in AndroidManifest.xml:\n");
1197
+ String[] missing = PermissionHelper.getUndefinedPermissions(getContext(), permStrings);
1198
+ for (String perm : missing) {
1199
+ builder.append(perm + "\n");
1200
+ }
1201
+ savedCall.reject(builder.toString());
1202
+ return false;
1203
+ }
1204
+
1205
+ return true;
1206
+ }
1207
+
1208
+ /**
1209
+ * Helper to check all permissions and see the current states of each permission.
1210
+ *
1211
+ * @since 3.0.0
1212
+ * @return A mapping of permission aliases to the associated granted status.
1213
+ */
1214
+ protected Map<String, PermissionState> getPermissionStates(Plugin plugin) {
1215
+ Map<String, PermissionState> permissionsResults = new HashMap<>();
1216
+ CapacitorPlugin annotation = plugin.getPluginHandle().getPluginAnnotation();
1217
+ for (Permission perm : annotation.permissions()) {
1218
+ // If a permission is defined with no permission constants, return GRANTED for it.
1219
+ // Otherwise, get its true state.
1220
+ if (perm.strings().length == 0 || (perm.strings().length == 1 && perm.strings()[0].isEmpty())) {
1221
+ String key = perm.alias();
1222
+ if (!key.isEmpty()) {
1223
+ PermissionState existingResult = permissionsResults.get(key);
1224
+
1225
+ // auto set permission state to GRANTED if the alias is empty.
1226
+ if (existingResult == null) {
1227
+ permissionsResults.put(key, PermissionState.GRANTED);
1228
+ }
1229
+ }
1230
+ } else {
1231
+ for (String permString : perm.strings()) {
1232
+ String key = perm.alias().isEmpty() ? permString : perm.alias();
1233
+ PermissionState permissionStatus;
1234
+ if (ActivityCompat.checkSelfPermission(this.getContext(), permString) == PackageManager.PERMISSION_GRANTED) {
1235
+ permissionStatus = PermissionState.GRANTED;
1236
+ } else {
1237
+ permissionStatus = PermissionState.PROMPT;
1238
+
1239
+ // Check if there is a cached permission state for the "Never ask again" state
1240
+ SharedPreferences prefs = getContext().getSharedPreferences(PERMISSION_PREFS_NAME, Activity.MODE_PRIVATE);
1241
+ String state = prefs.getString(permString, null);
1242
+
1243
+ if (state != null) {
1244
+ permissionStatus = PermissionState.byState(state);
1245
+ }
1246
+ }
1247
+
1248
+ PermissionState existingResult = permissionsResults.get(key);
1249
+
1250
+ // multiple permissions with the same alias must all be true, otherwise all false.
1251
+ if (existingResult == null || existingResult == PermissionState.GRANTED) {
1252
+ permissionsResults.put(key, permissionStatus);
1253
+ }
1254
+ }
1255
+ }
1256
+ }
1257
+
1258
+ return permissionsResults;
1259
+ }
1260
+
1261
+ /**
1262
+ * Handle an activity result and pass it to a plugin that has indicated it wants to
1263
+ * handle the result.
1264
+ * @param requestCode
1265
+ * @param resultCode
1266
+ * @param data
1267
+ */
1268
+ @SuppressWarnings("deprecation")
1269
+ boolean onActivityResult(int requestCode, int resultCode, Intent data) {
1270
+ PluginHandle plugin = getPluginWithRequestCode(requestCode);
1271
+
1272
+ if (plugin == null || plugin.getInstance() == null) {
1273
+ Logger.debug("Unable to find a Capacitor plugin to handle requestCode, trying Cordova plugins " + requestCode);
1274
+ return cordovaInterface.onActivityResult(requestCode, resultCode, data);
1275
+ }
1276
+
1277
+ // deprecated, to be removed
1278
+ PluginCall lastCall = plugin.getInstance().getSavedCall();
1279
+
1280
+ // If we don't have a saved last call (because our app was killed and restarted, for example),
1281
+ // Then we should see if we have any saved plugin call information and generate a new,
1282
+ // "dangling" plugin call (a plugin call that doesn't have a corresponding web callback)
1283
+ // and then send that to the plugin
1284
+ if (lastCall == null && pluginCallForLastActivity != null) {
1285
+ plugin.getInstance().saveCall(pluginCallForLastActivity);
1286
+ }
1287
+
1288
+ plugin.getInstance().handleOnActivityResult(requestCode, resultCode, data);
1289
+
1290
+ // Clear the plugin call we may have re-hydrated on app launch
1291
+ pluginCallForLastActivity = null;
1292
+
1293
+ return true;
1294
+ }
1295
+
1296
+ /**
1297
+ * Handle an onNewIntent lifecycle event and notify the plugins
1298
+ * @param intent
1299
+ */
1300
+ public void onNewIntent(Intent intent) {
1301
+ for (PluginHandle plugin : plugins.values()) {
1302
+ plugin.getInstance().handleOnNewIntent(intent);
1303
+ }
1304
+
1305
+ if (cordovaWebView != null) {
1306
+ cordovaWebView.onNewIntent(intent);
1307
+ }
1308
+ }
1309
+
1310
+ /**
1311
+ * Handle an onConfigurationChanged event and notify the plugins
1312
+ * @param newConfig
1313
+ */
1314
+ public void onConfigurationChanged(Configuration newConfig) {
1315
+ for (PluginHandle plugin : plugins.values()) {
1316
+ plugin.getInstance().handleOnConfigurationChanged(newConfig);
1317
+ }
1318
+ }
1319
+
1320
+ /**
1321
+ * Handle onRestart lifecycle event and notify the plugins
1322
+ */
1323
+ public void onRestart() {
1324
+ for (PluginHandle plugin : plugins.values()) {
1325
+ plugin.getInstance().handleOnRestart();
1326
+ }
1327
+ }
1328
+
1329
+ /**
1330
+ * Handle onStart lifecycle event and notify the plugins
1331
+ */
1332
+ public void onStart() {
1333
+ for (PluginHandle plugin : plugins.values()) {
1334
+ plugin.getInstance().handleOnStart();
1335
+ }
1336
+
1337
+ if (cordovaWebView != null) {
1338
+ cordovaWebView.handleStart();
1339
+ }
1340
+ }
1341
+
1342
+ /**
1343
+ * Handle onResume lifecycle event and notify the plugins
1344
+ */
1345
+ public void onResume() {
1346
+ for (PluginHandle plugin : plugins.values()) {
1347
+ plugin.getInstance().handleOnResume();
1348
+ }
1349
+
1350
+ if (cordovaWebView != null) {
1351
+ cordovaWebView.handleResume(this.shouldKeepRunning());
1352
+ }
1353
+ }
1354
+
1355
+ /**
1356
+ * Handle onPause lifecycle event and notify the plugins
1357
+ */
1358
+ public void onPause() {
1359
+ for (PluginHandle plugin : plugins.values()) {
1360
+ plugin.getInstance().handleOnPause();
1361
+ }
1362
+
1363
+ if (cordovaWebView != null) {
1364
+ boolean keepRunning = this.shouldKeepRunning() || cordovaInterface.getActivityResultCallback() != null;
1365
+ cordovaWebView.handlePause(keepRunning);
1366
+ }
1367
+ }
1368
+
1369
+ /**
1370
+ * Handle onStop lifecycle event and notify the plugins
1371
+ */
1372
+ public void onStop() {
1373
+ for (PluginHandle plugin : plugins.values()) {
1374
+ plugin.getInstance().handleOnStop();
1375
+ }
1376
+
1377
+ if (cordovaWebView != null) {
1378
+ cordovaWebView.handleStop();
1379
+ }
1380
+ }
1381
+
1382
+ /**
1383
+ * Handle onDestroy lifecycle event and notify the plugins
1384
+ */
1385
+ public void onDestroy() {
1386
+ for (PluginHandle plugin : plugins.values()) {
1387
+ plugin.getInstance().handleOnDestroy();
1388
+ }
1389
+
1390
+ handlerThread.quitSafely();
1391
+
1392
+ if (cordovaWebView != null) {
1393
+ cordovaWebView.handleDestroy();
1394
+ }
1395
+ }
1396
+
1397
+ /**
1398
+ * Handle onDetachedFromWindow lifecycle event
1399
+ */
1400
+ public void onDetachedFromWindow() {
1401
+ webView.removeAllViews();
1402
+ webView.destroy();
1403
+ }
1404
+
1405
+ public String getServerBasePath() {
1406
+ return this.localServer.getBasePath();
1407
+ }
1408
+
1409
+ /**
1410
+ * Tell the local server to load files from the given
1411
+ * file path instead of the assets path.
1412
+ * @param path
1413
+ */
1414
+ public void setServerBasePath(String path) {
1415
+ localServer.hostFiles(path);
1416
+ webView.post(() -> webView.loadUrl(appUrl));
1417
+ }
1418
+
1419
+ /**
1420
+ * Tell the local server to load files from the given
1421
+ * asset path.
1422
+ * @param path
1423
+ */
1424
+ public void setServerAssetPath(String path) {
1425
+ localServer.hostAssets(path);
1426
+ webView.post(() -> webView.loadUrl(appUrl));
1427
+ }
1428
+
1429
+ /**
1430
+ * Reload the WebView
1431
+ */
1432
+ public void reload() {
1433
+ webView.post(() -> webView.loadUrl(appUrl));
1434
+ }
1435
+
1436
+ public String getLocalUrl() {
1437
+ return localUrl;
1438
+ }
1439
+
1440
+ public WebViewLocalServer getLocalServer() {
1441
+ return localServer;
1442
+ }
1443
+
1444
+ public HostMask getAppAllowNavigationMask() {
1445
+ return appAllowNavigationMask;
1446
+ }
1447
+
1448
+ public Set<String> getAllowedOriginRules() {
1449
+ return allowedOriginRules;
1450
+ }
1451
+
1452
+ public BridgeWebViewClient getWebViewClient() {
1453
+ return this.webViewClient;
1454
+ }
1455
+
1456
+ public void setWebViewClient(BridgeWebViewClient client) {
1457
+ this.webViewClient = client;
1458
+ webView.setWebViewClient(client);
1459
+ }
1460
+
1461
+ List<WebViewListener> getWebViewListeners() {
1462
+ return webViewListeners;
1463
+ }
1464
+
1465
+ void setWebViewListeners(List<WebViewListener> webViewListeners) {
1466
+ this.webViewListeners = webViewListeners;
1467
+ }
1468
+
1469
+ RouteProcessor getRouteProcessor() {
1470
+ return routeProcessor;
1471
+ }
1472
+
1473
+ void setRouteProcessor(RouteProcessor routeProcessor) {
1474
+ this.routeProcessor = routeProcessor;
1475
+ }
1476
+
1477
+ ServerPath getServerPath() {
1478
+ return serverPath;
1479
+ }
1480
+
1481
+ /**
1482
+ * Add a listener that the WebViewClient can trigger on certain events.
1483
+ * @param webViewListener A {@link WebViewListener} to add.
1484
+ */
1485
+ public void addWebViewListener(WebViewListener webViewListener) {
1486
+ webViewListeners.add(webViewListener);
1487
+ }
1488
+
1489
+ /**
1490
+ * Remove a listener that the WebViewClient triggers on certain events.
1491
+ * @param webViewListener A {@link WebViewListener} to remove.
1492
+ */
1493
+ public void removeWebViewListener(WebViewListener webViewListener) {
1494
+ webViewListeners.remove(webViewListener);
1495
+ }
1496
+
1497
+ public static class Builder {
1498
+
1499
+ private Bundle instanceState = null;
1500
+ private CapConfig config = null;
1501
+ private List<Class<? extends Plugin>> plugins = new ArrayList<>();
1502
+ private List<Plugin> pluginInstances = new ArrayList<>();
1503
+ private AppCompatActivity activity;
1504
+ private Fragment fragment;
1505
+ private RouteProcessor routeProcessor;
1506
+ private final List<WebViewListener> webViewListeners = new ArrayList<>();
1507
+ private ServerPath serverPath;
1508
+
1509
+ public Builder(AppCompatActivity activity) {
1510
+ this.activity = activity;
1511
+ }
1512
+
1513
+ public Builder(Fragment fragment) {
1514
+ this.activity = (AppCompatActivity) fragment.getActivity();
1515
+ this.fragment = fragment;
1516
+ }
1517
+
1518
+ public Builder setInstanceState(Bundle instanceState) {
1519
+ this.instanceState = instanceState;
1520
+ return this;
1521
+ }
1522
+
1523
+ public Builder setConfig(CapConfig config) {
1524
+ this.config = config;
1525
+ return this;
1526
+ }
1527
+
1528
+ public Builder setPlugins(List<Class<? extends Plugin>> plugins) {
1529
+ this.plugins = plugins;
1530
+ return this;
1531
+ }
1532
+
1533
+ public Builder addPlugin(Class<? extends Plugin> plugin) {
1534
+ this.plugins.add(plugin);
1535
+ return this;
1536
+ }
1537
+
1538
+ public Builder addPlugins(List<Class<? extends Plugin>> plugins) {
1539
+ for (Class<? extends Plugin> cls : plugins) {
1540
+ this.addPlugin(cls);
1541
+ }
1542
+
1543
+ return this;
1544
+ }
1545
+
1546
+ public Builder addPluginInstance(Plugin plugin) {
1547
+ this.pluginInstances.add(plugin);
1548
+ return this;
1549
+ }
1550
+
1551
+ public Builder addPluginInstances(List<Plugin> plugins) {
1552
+ this.pluginInstances.addAll(plugins);
1553
+ return this;
1554
+ }
1555
+
1556
+ public Builder addWebViewListener(WebViewListener webViewListener) {
1557
+ webViewListeners.add(webViewListener);
1558
+ return this;
1559
+ }
1560
+
1561
+ public Builder addWebViewListeners(List<WebViewListener> webViewListeners) {
1562
+ for (WebViewListener listener : webViewListeners) {
1563
+ this.addWebViewListener(listener);
1564
+ }
1565
+
1566
+ return this;
1567
+ }
1568
+
1569
+ public Builder setRouteProcessor(RouteProcessor routeProcessor) {
1570
+ this.routeProcessor = routeProcessor;
1571
+ return this;
1572
+ }
1573
+
1574
+ public Builder setServerPath(ServerPath serverPath) {
1575
+ this.serverPath = serverPath;
1576
+ return this;
1577
+ }
1578
+
1579
+ public Bridge create() {
1580
+ // Cordova initialization
1581
+ ConfigXmlParser parser = new ConfigXmlParser();
1582
+ parser.parse(activity.getApplicationContext());
1583
+ CordovaPreferences preferences = parser.getPreferences();
1584
+ preferences.setPreferencesBundle(activity.getIntent().getExtras());
1585
+ List<PluginEntry> pluginEntries = parser.getPluginEntries();
1586
+
1587
+ MockCordovaInterfaceImpl cordovaInterface = new MockCordovaInterfaceImpl(activity);
1588
+ if (instanceState != null) {
1589
+ cordovaInterface.restoreInstanceState(instanceState);
1590
+ }
1591
+
1592
+ WebView webView = this.fragment != null ? fragment.getView().findViewById(R.id.webview) : activity.findViewById(R.id.webview);
1593
+ MockCordovaWebViewImpl mockWebView = new MockCordovaWebViewImpl(activity.getApplicationContext());
1594
+ mockWebView.init(cordovaInterface, pluginEntries, preferences, webView);
1595
+ PluginManager pluginManager = mockWebView.getPluginManager();
1596
+ cordovaInterface.onCordovaInit(pluginManager);
1597
+
1598
+ // Bridge initialization
1599
+ Bridge bridge = new Bridge(
1600
+ activity,
1601
+ serverPath,
1602
+ fragment,
1603
+ webView,
1604
+ plugins,
1605
+ pluginInstances,
1606
+ cordovaInterface,
1607
+ pluginManager,
1608
+ preferences,
1609
+ config
1610
+ );
1611
+
1612
+ if (webView instanceof CapacitorWebView capacitorWebView) {
1613
+ capacitorWebView.setBridge(bridge);
1614
+ }
1615
+
1616
+ bridge.setCordovaWebView(mockWebView);
1617
+ bridge.setWebViewListeners(webViewListeners);
1618
+ bridge.setRouteProcessor(routeProcessor);
1619
+
1620
+ if (instanceState != null) {
1621
+ bridge.restoreInstanceState(instanceState);
1622
+ }
1623
+
1624
+ return bridge;
1625
+ }
1626
+ }
1627
+ }