@capgo/capacitor-inappbrowser 8.6.17 → 8.6.19

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.
@@ -1,59 +1,12 @@
1
1
  package ee.forgr.capacitor_inappbrowser;
2
2
 
3
3
  import android.Manifest;
4
- import android.app.Activity;
5
- import android.content.ComponentName;
6
- import android.content.Intent;
7
- import android.content.pm.PackageManager;
8
- import android.content.pm.ResolveInfo;
9
- import android.graphics.Bitmap;
10
- import android.graphics.BitmapFactory;
11
- import android.graphics.Canvas;
12
- import android.graphics.Color;
13
- import android.graphics.drawable.Drawable;
14
- import android.net.Uri;
15
- import android.os.Bundle;
16
- import android.os.Message;
17
- import android.text.TextUtils;
18
- import android.util.Log;
19
- import android.webkit.CookieManager;
20
- import android.webkit.PermissionRequest;
21
- import android.webkit.WebView;
22
- import androidx.activity.result.ActivityResult;
23
- import androidx.activity.result.ActivityResultLauncher;
24
- import androidx.activity.result.contract.ActivityResultContracts;
25
- import androidx.annotation.NonNull;
26
- import androidx.appcompat.content.res.AppCompatResources;
27
- import androidx.browser.customtabs.CustomTabColorSchemeParams;
28
- import androidx.browser.customtabs.CustomTabsCallback;
29
- import androidx.browser.customtabs.CustomTabsClient;
30
- import androidx.browser.customtabs.CustomTabsIntent;
31
- import androidx.browser.customtabs.CustomTabsServiceConnection;
32
- import androidx.browser.customtabs.CustomTabsSession;
33
- import androidx.core.content.ContextCompat;
34
- import com.getcapacitor.JSArray;
35
- import com.getcapacitor.JSObject;
36
- import com.getcapacitor.Plugin;
37
- import com.getcapacitor.PluginCall;
38
- import com.getcapacitor.PluginMethod;
39
4
  import com.getcapacitor.annotation.CapacitorPlugin;
40
5
  import com.getcapacitor.annotation.Permission;
41
- import java.lang.reflect.Field;
42
- import java.util.ArrayDeque;
43
- import java.util.ArrayList;
44
- import java.util.Deque;
45
- import java.util.HashMap;
46
- import java.util.Iterator;
47
- import java.util.List;
48
- import java.util.Map;
49
- import java.util.UUID;
50
- import java.util.regex.Pattern;
51
- import java.util.regex.PatternSyntaxException;
52
- import org.json.JSONException;
53
- import org.json.JSONObject;
54
6
 
7
+ @Deprecated
55
8
  @CapacitorPlugin(
56
- name = "InAppBrowser",
9
+ name = "CapgoInAppBrowser",
57
10
  permissions = {
58
11
  @Permission(alias = "camera", strings = { Manifest.permission.CAMERA }),
59
12
  @Permission(alias = "microphone", strings = { Manifest.permission.RECORD_AUDIO }),
@@ -61,1760 +14,4 @@ import org.json.JSONObject;
61
14
  },
62
15
  requestCodes = { WebViewDialog.FILE_CHOOSER_REQUEST_CODE }
63
16
  )
64
- public class InAppBrowserPlugin extends Plugin implements WebViewDialog.PermissionHandler {
65
-
66
- private final String pluginVersion = "8.6.17";
67
-
68
- public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome"; // Change when in stable
69
- private CustomTabsClient customTabsClient;
70
- private CustomTabsSession currentSession;
71
- private WebViewDialog webViewDialog = null;
72
- private final Map<String, WebViewDialog> webViewDialogs = new HashMap<>();
73
- private final Deque<String> webViewStack = new ArrayDeque<>();
74
- private String activeWebViewId = null;
75
- private String currentUrl = "";
76
-
77
- private PermissionRequest currentPermissionRequest;
78
- private boolean currentPermissionNeedsCamera = false;
79
- private boolean currentPermissionNeedsMicrophone = false;
80
-
81
- private ActivityResultLauncher<Intent> fileChooserLauncher;
82
- private ActivityResultLauncher<String[]> webPermissionLauncher;
83
- private PluginCall pendingCameraPermissionCall;
84
-
85
- private PluginCall openSecureWindowSavedCall;
86
- private String openSecureWindowRedirectUri;
87
-
88
- @Override
89
- public void load() {
90
- super.load();
91
- fileChooserLauncher = getActivity().registerForActivityResult(
92
- new ActivityResultContracts.StartActivityForResult(),
93
- this::handleFileChooserResult
94
- );
95
- webPermissionLauncher = getActivity().registerForActivityResult(
96
- new ActivityResultContracts.RequestMultiplePermissions(),
97
- this::handleWebPermissionResult
98
- );
99
- }
100
-
101
- private List<NativeProxyRule> parseProxyRules(JSArray rawRules, String optionName, PluginCall call) {
102
- List<NativeProxyRule> parsed = new ArrayList<>();
103
- if (rawRules == null) {
104
- return parsed;
105
- }
106
-
107
- for (int index = 0; index < rawRules.length(); index++) {
108
- try {
109
- JSONObject rule = rawRules.getJSONObject(index);
110
- parsed.add(
111
- new NativeProxyRule(
112
- rule.optString("id", null),
113
- NativeProxyRule.compilePattern(rule.optString("urlRegex", null)),
114
- NativeProxyRule.compilePattern(rule.optString("methodRegex", null)),
115
- NativeProxyRule.compilePattern(rule.optString("headerRegex", null)),
116
- NativeProxyRule.compilePattern(rule.optString("bodyRegex", null)),
117
- NativeProxyRule.compilePattern(rule.optString("statusRegex", null)),
118
- NativeProxyRule.compilePattern(rule.optString("responseHeaderRegex", null)),
119
- NativeProxyRule.compilePattern(rule.optString("responseBodyRegex", null)),
120
- rule.optBoolean("mainFrameOnly", false),
121
- NativeProxyRule.Action.fromString(rule.optString("action", "continue"))
122
- )
123
- );
124
- } catch (JSONException | PatternSyntaxException error) {
125
- call.reject("Invalid " + optionName + " at index " + index + ": " + error.getMessage());
126
- return null;
127
- }
128
- }
129
-
130
- return parsed;
131
- }
132
-
133
- private WebViewCallbacks buildCallbacks(String webViewId) {
134
- return new WebViewCallbacks() {
135
- @Override
136
- public void urlChangeEvent(String url) {
137
- notifyListeners("urlChangeEvent", new JSObject().put("id", webViewId).put("url", url));
138
- if (webViewId.equals(activeWebViewId)) {
139
- currentUrl = url;
140
- }
141
- }
142
-
143
- @Override
144
- public void closeEvent(String url) {
145
- notifyListeners("closeEvent", new JSObject().put("id", webViewId).put("url", url));
146
- unregisterWebView(webViewId);
147
- }
148
-
149
- @Override
150
- public void pageLoaded() {
151
- notifyListeners("browserPageLoaded", new JSObject().put("id", webViewId));
152
- }
153
-
154
- @Override
155
- public void pageLoadError() {
156
- notifyListeners("pageLoadError", new JSObject().put("id", webViewId));
157
- }
158
-
159
- @Override
160
- public void customSchemeIntercepted(String url, boolean opened) {
161
- notifyListeners("customSchemeIntercepted", new JSObject().put("id", webViewId).put("url", url).put("opened", opened));
162
- }
163
-
164
- @Override
165
- public void buttonNearDoneClicked() {
166
- notifyListeners("buttonNearDoneClick", new JSObject().put("id", webViewId));
167
- }
168
-
169
- @Override
170
- public void confirmBtnClicked(String url) {
171
- notifyListeners("confirmBtnClicked", new JSObject().put("id", webViewId).put("url", url));
172
- }
173
-
174
- @Override
175
- public void screenshotTaken(JSObject screenshot) {
176
- JSObject event = new JSObject();
177
- Iterator<String> keys = screenshot.keys();
178
- while (keys.hasNext()) {
179
- String key = keys.next();
180
- event.put(key, screenshot.opt(key));
181
- }
182
- event.put("id", webViewId);
183
- notifyListeners("screenshotTaken", event);
184
- }
185
-
186
- @Override
187
- public void downloadCompleted(
188
- String sourceUrl,
189
- String fileName,
190
- String mimeType,
191
- String path,
192
- String localUrl,
193
- String handledBy
194
- ) {
195
- JSObject event = new JSObject();
196
- event.put("id", webViewId);
197
- if (sourceUrl != null) {
198
- event.put("sourceUrl", sourceUrl);
199
- }
200
- event.put("fileName", fileName);
201
- if (mimeType != null) {
202
- event.put("mimeType", mimeType);
203
- }
204
- event.put("path", path);
205
- event.put("localUrl", localUrl);
206
- event.put("handledBy", handledBy);
207
- notifyListeners("downloadCompleted", event);
208
- }
209
-
210
- @Override
211
- public void downloadFailed(String sourceUrl, String fileName, String mimeType, String error) {
212
- JSObject event = new JSObject();
213
- event.put("id", webViewId);
214
- if (sourceUrl != null) {
215
- event.put("sourceUrl", sourceUrl);
216
- }
217
- if (fileName != null) {
218
- event.put("fileName", fileName);
219
- }
220
- if (mimeType != null) {
221
- event.put("mimeType", mimeType);
222
- }
223
- event.put("error", error);
224
- notifyListeners("downloadFailed", event);
225
- }
226
-
227
- @Override
228
- public void proxyRequestEvent(
229
- String requestId,
230
- String phase,
231
- String url,
232
- String method,
233
- String headersJson,
234
- String body,
235
- Integer status,
236
- String responseHeadersJson,
237
- String responseBody,
238
- String dialogWebViewId
239
- ) {
240
- JSObject event = new JSObject();
241
- event.put("requestId", requestId);
242
- event.put("phase", phase);
243
- event.put("url", url);
244
- event.put("method", method);
245
- try {
246
- event.put("headers", new JSObject(headersJson));
247
- } catch (Exception ignored) {
248
- event.put("headers", new JSObject());
249
- }
250
- event.put("body", body);
251
- if (status != null) {
252
- event.put("status", status);
253
- }
254
- if (responseHeadersJson != null) {
255
- try {
256
- event.put("responseHeaders", new JSObject(responseHeadersJson));
257
- } catch (Exception ignored) {
258
- event.put("responseHeaders", new JSObject());
259
- }
260
- }
261
- event.put("responseBody", responseBody);
262
- event.put("webviewId", dialogWebViewId);
263
- notifyListeners("proxyRequest", event);
264
- }
265
-
266
- @Override
267
- public void javascriptCallback(String message) {
268
- Log.d("WebViewDialog", "Received message from JavaScript: " + message);
269
- try {
270
- JSONObject jsonMessage = new JSONObject(message);
271
- JSObject jsObject = new JSObject();
272
- Iterator<String> keys = jsonMessage.keys();
273
- while (keys.hasNext()) {
274
- String key = keys.next();
275
- jsObject.put(key, jsonMessage.get(key));
276
- }
277
- jsObject.put("id", webViewId);
278
- notifyListeners("messageFromWebview", jsObject);
279
- } catch (JSONException e) {
280
- Log.e("WebViewDialog", "Error parsing JSON message: " + e.getMessage());
281
- JSObject jsObject = new JSObject();
282
- jsObject.put("rawMessage", message);
283
- jsObject.put("id", webViewId);
284
- notifyListeners("messageFromWebview", jsObject);
285
- }
286
- }
287
-
288
- @Override
289
- public void consoleMessage(String level, String message, String source, Integer line, Integer column) {
290
- notifyListeners("consoleMessage", ConsoleMessageEventHelper.create(webViewId, level, message, source, line, column));
291
- }
292
- };
293
- }
294
-
295
- private WebViewDialog createManagedDialog(String webViewId, Options options) {
296
- return createManagedDialog(webViewId, options, true);
297
- }
298
-
299
- private WebViewDialog createManagedDialog(String webViewId, Options options, boolean makeActive) {
300
- WebViewDialog dialog = new WebViewDialog(
301
- getContext(),
302
- android.R.style.Theme_NoTitleBar,
303
- options,
304
- InAppBrowserPlugin.this,
305
- getBridge().getWebView()
306
- );
307
- dialog.setInstanceId(webViewId);
308
- dialog.activity = InAppBrowserPlugin.this.getActivity();
309
- registerWebView(webViewId, dialog, makeActive);
310
- return dialog;
311
- }
312
-
313
- private void notifyPopupWindowOpened(String popupId, String parentId, String popupUrl, boolean visible) {
314
- JSObject event = new JSObject();
315
- event.put("id", popupId);
316
- if (parentId != null && !parentId.isEmpty()) {
317
- event.put("parentId", parentId);
318
- }
319
- if (popupUrl != null && !popupUrl.isEmpty()) {
320
- event.put("url", popupUrl);
321
- }
322
- event.put("visible", visible);
323
- notifyListeners("popupWindowOpened", event);
324
- }
325
-
326
- @Override
327
- public boolean createManagedPopupWindow(WebViewDialog parentDialog, Message resultMsg, boolean isUserGesture, String popupUrl) {
328
- if (resultMsg == null || !(resultMsg.obj instanceof WebView.WebViewTransport) || parentDialog == null) {
329
- return false;
330
- }
331
-
332
- Options parentOptions = parentDialog.getOptions();
333
- if (parentOptions == null) {
334
- return false;
335
- }
336
-
337
- String popupWebViewId = UUID.randomUUID().toString();
338
- Options popupOptions = parentOptions.copyForPopup();
339
- popupOptions.setCallbacks(buildCallbacks(popupWebViewId));
340
- popupOptions.setPluginCall(null);
341
-
342
- boolean shouldActivatePopup = ActiveWebViewSupport.shouldActivateNewWebView(popupOptions.isHidden(), activeWebViewId != null);
343
- WebViewDialog popupDialog = createManagedDialog(popupWebViewId, popupOptions, shouldActivatePopup);
344
- if (popupOptions.isHidden()) {
345
- popupDialog.setHidden(true);
346
- }
347
- popupDialog.presentWebView();
348
-
349
- WebView popupWebView = popupDialog.getManagedWebView();
350
- if (popupWebView == null) {
351
- unregisterWebView(popupWebViewId);
352
- return false;
353
- }
354
-
355
- WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
356
- transport.setWebView(popupWebView);
357
- resultMsg.sendToTarget();
358
- notifyPopupWindowOpened(popupWebViewId, parentDialog.getInstanceId(), popupUrl, !popupOptions.isHidden());
359
- return true;
360
- }
361
-
362
- private void registerWebView(String id, WebViewDialog dialog, boolean makeActive) {
363
- webViewDialogs.put(id, dialog);
364
- webViewStack.remove(id);
365
- webViewStack.addLast(id);
366
- if (makeActive || activeWebViewId == null || webViewDialog == null) {
367
- setActiveWebView(id, dialog);
368
- }
369
- }
370
-
371
- private void setActiveWebView(String id, WebViewDialog dialog) {
372
- activeWebViewId = id;
373
- webViewDialog = dialog;
374
- }
375
-
376
- private void unregisterWebView(String id) {
377
- webViewDialogs.remove(id);
378
- webViewStack.remove(id);
379
- activeWebViewId = webViewStack.peekLast();
380
- webViewDialog = activeWebViewId != null ? webViewDialogs.get(activeWebViewId) : null;
381
- if (webViewDialog != null) {
382
- String url = webViewDialog.getUrl();
383
- if (url != null) {
384
- currentUrl = url;
385
- }
386
- }
387
- }
388
-
389
- private String resolveTargetId(PluginCall call) {
390
- String id = call.getString("id");
391
- return id != null ? id : activeWebViewId;
392
- }
393
-
394
- private WebViewDialog resolveDialog(String id) {
395
- if (id != null) {
396
- return webViewDialogs.get(id);
397
- }
398
- if (activeWebViewId != null) {
399
- WebViewDialog dialog = webViewDialogs.get(activeWebViewId);
400
- if (dialog != null) {
401
- return dialog;
402
- }
403
- }
404
- return webViewDialog;
405
- }
406
-
407
- private ArrayList<WebView> cacheWebViewsFor(String targetId) {
408
- ArrayList<WebView> targetWebViews = new ArrayList<>();
409
- if (targetId != null) {
410
- WebViewDialog dialog = webViewDialogs.get(targetId);
411
- if (dialog != null && dialog.getManagedWebView() != null) {
412
- targetWebViews.add(dialog.getManagedWebView());
413
- }
414
- return targetWebViews;
415
- }
416
-
417
- for (WebViewDialog dialog : webViewDialogs.values()) {
418
- WebView managedWebView = dialog.getManagedWebView();
419
- if (managedWebView != null) {
420
- targetWebViews.add(managedWebView);
421
- }
422
- }
423
-
424
- if (targetWebViews.isEmpty() && getBridge() != null && getBridge().getWebView() != null) {
425
- targetWebViews.add(getBridge().getWebView());
426
- }
427
- return targetWebViews;
428
- }
429
-
430
- private WebViewDialog findFileChooserDialog() {
431
- if (activeWebViewId != null) {
432
- WebViewDialog dialog = webViewDialogs.get(activeWebViewId);
433
- if (dialog != null && dialog.mFilePathCallback != null) {
434
- return dialog;
435
- }
436
- }
437
-
438
- if (webViewDialog != null && webViewDialog.mFilePathCallback != null) {
439
- return webViewDialog;
440
- }
441
-
442
- for (WebViewDialog dialog : webViewDialogs.values()) {
443
- if (dialog != null && dialog.mFilePathCallback != null) {
444
- return dialog;
445
- }
446
- }
447
-
448
- return null;
449
- }
450
-
451
- private void handleFileChooserResult(ActivityResult result) {
452
- WebViewDialog webViewDialog = findFileChooserDialog();
453
- if (webViewDialog != null && webViewDialog.mFilePathCallback != null) {
454
- Uri[] results = null;
455
- Intent data = result.getData();
456
-
457
- if (result.getResultCode() == Activity.RESULT_OK) {
458
- // Handle camera capture result
459
- if (webViewDialog.tempCameraUri != null && (data == null || data.getData() == null)) {
460
- results = new Uri[] { webViewDialog.tempCameraUri };
461
- }
462
- // Handle regular file picker result
463
- else if (data != null) {
464
- if (data.getClipData() != null) {
465
- // Handle multiple files
466
- int count = data.getClipData().getItemCount();
467
- results = new Uri[count];
468
- for (int i = 0; i < count; i++) {
469
- results[i] = data.getClipData().getItemAt(i).getUri();
470
- }
471
- } else if (data.getData() != null) {
472
- // Handle single file
473
- results = new Uri[] { data.getData() };
474
- }
475
- }
476
- }
477
-
478
- // Send the result to WebView and clean up
479
- webViewDialog.mFilePathCallback.onReceiveValue(results);
480
- webViewDialog.mFilePathCallback = null;
481
- webViewDialog.tempCameraUri = null;
482
- }
483
- }
484
-
485
- public void handleMicrophonePermissionRequest(PermissionRequest request) {
486
- if (request == null) {
487
- return;
488
- }
489
-
490
- handleWebPermissionRequest(
491
- request,
492
- WebViewPermissionSupport.hasResource(request.getResources(), PermissionRequest.RESOURCE_VIDEO_CAPTURE),
493
- true
494
- );
495
- }
496
-
497
- public void handleCameraPermissionRequest(PermissionRequest request) {
498
- if (request == null) {
499
- return;
500
- }
501
-
502
- handleWebPermissionRequest(
503
- request,
504
- true,
505
- WebViewPermissionSupport.hasResource(request.getResources(), PermissionRequest.RESOURCE_AUDIO_CAPTURE)
506
- );
507
- }
508
-
509
- private boolean isAndroidPermissionGranted(String permission) {
510
- return ContextCompat.checkSelfPermission(getContext(), permission) == PackageManager.PERMISSION_GRANTED;
511
- }
512
-
513
- private void handleWebPermissionRequest(PermissionRequest request, boolean needsCamera, boolean needsMicrophone) {
514
- if (request == null) {
515
- return;
516
- }
517
-
518
- if (!needsCamera && !needsMicrophone) {
519
- request.deny();
520
- return;
521
- }
522
-
523
- if (currentPermissionRequest != null && currentPermissionRequest != request) {
524
- try {
525
- currentPermissionRequest.deny();
526
- } catch (Exception e) {
527
- Log.w(getLogTag(), "Could not deny previous WebView permission request: " + e.getMessage());
528
- }
529
- }
530
-
531
- currentPermissionRequest = request;
532
- currentPermissionNeedsCamera = needsCamera;
533
- currentPermissionNeedsMicrophone = needsMicrophone;
534
-
535
- String[] missingPermissions = WebViewPermissionSupport.missingAndroidPermissions(
536
- needsCamera,
537
- needsMicrophone,
538
- isAndroidPermissionGranted(Manifest.permission.CAMERA),
539
- isAndroidPermissionGranted(Manifest.permission.RECORD_AUDIO)
540
- );
541
-
542
- if (missingPermissions.length == 0) {
543
- grantCurrentWebPermissionRequest();
544
- } else if (webPermissionLauncher != null) {
545
- webPermissionLauncher.launch(missingPermissions);
546
- } else {
547
- denyCurrentWebPermissionRequest();
548
- }
549
- }
550
-
551
- private void handleWebPermissionResult(Map<String, Boolean> result) {
552
- if (currentPermissionRequest != null) {
553
- boolean cameraGranted = !currentPermissionNeedsCamera || isAndroidPermissionGranted(Manifest.permission.CAMERA);
554
- boolean microphoneGranted = !currentPermissionNeedsMicrophone || isAndroidPermissionGranted(Manifest.permission.RECORD_AUDIO);
555
-
556
- if (cameraGranted && microphoneGranted) {
557
- grantCurrentWebPermissionRequest();
558
- } else {
559
- denyCurrentWebPermissionRequest();
560
- }
561
- return;
562
- }
563
-
564
- if (pendingCameraPermissionCall != null) {
565
- PluginCall call = pendingCameraPermissionCall;
566
- pendingCameraPermissionCall = null;
567
- if (isAndroidPermissionGranted(Manifest.permission.CAMERA)) {
568
- call.resolve();
569
- } else {
570
- call.reject("Camera permission is required");
571
- }
572
- }
573
- }
574
-
575
- private void grantCurrentWebPermissionRequest() {
576
- if (currentPermissionRequest != null) {
577
- currentPermissionRequest.grant(
578
- WebViewPermissionSupport.grantResources(currentPermissionNeedsCamera, currentPermissionNeedsMicrophone)
579
- );
580
- clearCurrentWebPermissionRequest();
581
- }
582
- }
583
-
584
- private void denyCurrentWebPermissionRequest() {
585
- if (currentPermissionRequest != null) {
586
- currentPermissionRequest.deny();
587
- clearCurrentWebPermissionRequest();
588
- }
589
- }
590
-
591
- @Override
592
- public void clearPendingPermissionRequest(PermissionRequest request) {
593
- if (currentPermissionRequest == request) {
594
- clearCurrentWebPermissionRequest();
595
- }
596
- }
597
-
598
- private void clearCurrentWebPermissionRequest() {
599
- currentPermissionRequest = null;
600
- currentPermissionNeedsCamera = false;
601
- currentPermissionNeedsMicrophone = false;
602
- }
603
-
604
- CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
605
- @Override
606
- public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
607
- customTabsClient = client;
608
- }
609
-
610
- @Override
611
- public void onServiceDisconnected(ComponentName name) {
612
- customTabsClient = null;
613
- }
614
- };
615
-
616
- @PluginMethod
617
- public void requestCameraPermission(PluginCall call) {
618
- if (isAndroidPermissionGranted(Manifest.permission.CAMERA)) {
619
- call.resolve();
620
- return;
621
- }
622
-
623
- if (webPermissionLauncher == null) {
624
- call.reject("Camera permission request is unavailable");
625
- return;
626
- }
627
-
628
- if (pendingCameraPermissionCall != null) {
629
- pendingCameraPermissionCall.reject("Camera permission request was replaced");
630
- }
631
-
632
- pendingCameraPermissionCall = call;
633
- webPermissionLauncher.launch(new String[] { Manifest.permission.CAMERA });
634
- }
635
-
636
- @PluginMethod
637
- public void setUrl(PluginCall call) {
638
- String url = call.getString("url");
639
- if (url == null || TextUtils.isEmpty(url)) {
640
- call.reject("Invalid URL");
641
- return;
642
- }
643
-
644
- String targetId = call.getString("id");
645
- WebViewDialog webViewDialog = resolveDialog(targetId);
646
- if (webViewDialog == null) {
647
- call.reject("WebView is not initialized");
648
- return;
649
- }
650
-
651
- currentUrl = url;
652
- this.getActivity().runOnUiThread(
653
- new Runnable() {
654
- @Override
655
- public void run() {
656
- try {
657
- webViewDialog.setUrl(url);
658
- call.resolve();
659
- } catch (Exception e) {
660
- Log.e("InAppBrowser", "Error setting URL: " + e.getMessage());
661
- call.reject("Failed to set URL: " + e.getMessage());
662
- }
663
- }
664
- }
665
- );
666
- }
667
-
668
- @PluginMethod
669
- public void open(PluginCall call) {
670
- String url = call.getString("url");
671
- if (url == null) {
672
- call.reject("URL is required");
673
- return;
674
- }
675
-
676
- // get the deeplink prevention, if provided
677
- Boolean preventDeeplink = call.getBoolean("preventDeeplink", false);
678
- Boolean isPresentAfterPageLoad = call.getBoolean("isPresentAfterPageLoad", false);
679
-
680
- if (TextUtils.isEmpty(url)) {
681
- call.reject("Invalid URL");
682
- }
683
- currentUrl = url;
684
- CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(getCustomTabsSession());
685
-
686
- // --- Chrome Custom Tab UI customization ---
687
-
688
- // Toolbar color (applied to both light and dark color schemes)
689
- String toolbarColor = call.getString("toolbarColor");
690
- if (toolbarColor != null) {
691
- try {
692
- int colorInt = Color.parseColor(toolbarColor);
693
- CustomTabColorSchemeParams colorParams = new CustomTabColorSchemeParams.Builder()
694
- .setToolbarColor(colorInt)
695
- .setNavigationBarColor(colorInt)
696
- .build();
697
- builder.setDefaultColorSchemeParams(colorParams);
698
- builder.setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, colorParams);
699
- } catch (IllegalArgumentException e) {
700
- Log.w(getLogTag(), "Invalid toolbarColor value: " + toolbarColor, e);
701
- }
702
- }
703
-
704
- // Auto-hide URL bar on scroll
705
- if (call.getBoolean("urlBarHidingEnabled", false)) {
706
- builder.setUrlBarHidingEnabled(true);
707
- }
708
-
709
- // Show page <title> instead of URL
710
- if (call.getBoolean("showTitle", false)) {
711
- builder.setShowTitle(true);
712
- }
713
-
714
- // Replace X close icon with a back arrow
715
- if (call.getBoolean("showArrow", false)) {
716
- Drawable arrowDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.arrow_back_enabled);
717
- if (arrowDrawable != null) {
718
- Bitmap backArrow = Bitmap.createBitmap(
719
- arrowDrawable.getIntrinsicWidth(),
720
- arrowDrawable.getIntrinsicHeight(),
721
- Bitmap.Config.ARGB_8888
722
- );
723
- Canvas canvas = new Canvas(backArrow);
724
- arrowDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
725
- arrowDrawable.draw(canvas);
726
- builder.setCloseButtonIcon(backArrow);
727
- }
728
- }
729
-
730
- // Remove share action from overflow menu
731
- if (call.getBoolean("disableShare", false)) {
732
- builder.setShareState(CustomTabsIntent.SHARE_STATE_OFF);
733
- }
734
-
735
- CustomTabsIntent tabsIntent = builder.build();
736
-
737
- // Undocumented Chromium flags — these may stop working on future Chrome updates
738
- if (call.getBoolean("disableBookmark", false)) {
739
- tabsIntent.intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_STAR_BUTTON", true);
740
- }
741
- if (call.getBoolean("disableDownload", false)) {
742
- tabsIntent.intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON", true);
743
- }
744
-
745
- tabsIntent.intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(Intent.URI_ANDROID_APP_SCHEME + "//" + getContext().getPackageName()));
746
- tabsIntent.intent.putExtra(android.provider.Browser.EXTRA_HEADERS, this.getHeaders(call));
747
-
748
- if (preventDeeplink != false) {
749
- String browserPackageName = "";
750
- Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
751
- ResolveInfo resolveInfo = getContext().getPackageManager().resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY);
752
-
753
- if (resolveInfo != null) {
754
- browserPackageName = resolveInfo.activityInfo.packageName;
755
-
756
- if (!browserPackageName.isEmpty()) {
757
- tabsIntent.intent.setPackage(browserPackageName);
758
- }
759
- }
760
- }
761
-
762
- if (isPresentAfterPageLoad) {
763
- tabsIntent.intent.putExtra("isPresentAfterPageLoad", true);
764
- }
765
-
766
- tabsIntent.launchUrl(getContext(), Uri.parse(url));
767
-
768
- call.resolve();
769
- }
770
-
771
- @PluginMethod
772
- public void clearCache(PluginCall call) {
773
- String targetId = call.getString("id");
774
- this.getActivity().runOnUiThread(() -> {
775
- ArrayList<WebView> targetWebViews = cacheWebViewsFor(targetId);
776
- if (targetWebViews.isEmpty()) {
777
- call.reject("WebView is not initialized");
778
- return;
779
- }
780
-
781
- for (WebView targetWebView : targetWebViews) {
782
- targetWebView.clearCache(true);
783
- }
784
- call.resolve();
785
- });
786
- }
787
-
788
- @PluginMethod
789
- public void clearAllCookies(PluginCall call) {
790
- CookieManager cookieManager = CookieManager.getInstance();
791
- cookieManager.removeAllCookies(null);
792
- cookieManager.flush();
793
- call.resolve();
794
- }
795
-
796
- @PluginMethod
797
- public void clearCookies(PluginCall call) {
798
- String url = call.getString("url", currentUrl);
799
- if (url == null || TextUtils.isEmpty(url)) {
800
- call.reject("Invalid URL");
801
- return;
802
- }
803
-
804
- Uri uri = Uri.parse(url);
805
- String host = uri.getHost();
806
- if (host == null || TextUtils.isEmpty(host)) {
807
- call.reject("Invalid URL (Host is null)");
808
- return;
809
- }
810
-
811
- CookieManager cookieManager = CookieManager.getInstance();
812
- String cookieString = cookieManager.getCookie(url);
813
- ArrayList<String> cookiesToRemove = new ArrayList<>();
814
-
815
- if (cookieString != null) {
816
- String[] cookies = cookieString.split("; ");
817
-
818
- String domain = uri.getHost();
819
-
820
- for (String cookie : cookies) {
821
- String[] parts = cookie.split("=");
822
- if (parts.length > 0) {
823
- cookiesToRemove.add(parts[0].trim());
824
- CookieManager.getInstance().setCookie(url, String.format("%s=del;", parts[0].trim()));
825
- }
826
- }
827
- }
828
-
829
- StringBuilder scriptToRun = new StringBuilder();
830
- for (String cookieToRemove : cookiesToRemove) {
831
- scriptToRun.append(
832
- String.format("window.cookieStore.delete('%s', {name: '%s', domain: '%s'});", cookieToRemove, cookieToRemove, url)
833
- );
834
- }
835
-
836
- Log.i("DelCookies", String.format("Script to run:\n%s", scriptToRun));
837
-
838
- String targetId = call.getString("id");
839
- ArrayList<WebViewDialog> targetDialogs = new ArrayList<>();
840
-
841
- if (targetId != null) {
842
- WebViewDialog dialog = webViewDialogs.get(targetId);
843
- if (dialog == null) {
844
- call.reject("WebView is not initialized");
845
- return;
846
- }
847
- targetDialogs.add(dialog);
848
- } else if (!webViewDialogs.isEmpty()) {
849
- targetDialogs.addAll(webViewDialogs.values());
850
- }
851
-
852
- this.getActivity().runOnUiThread(
853
- new Runnable() {
854
- @Override
855
- public void run() {
856
- try {
857
- if (!targetDialogs.isEmpty()) {
858
- for (WebViewDialog dialog : targetDialogs) {
859
- dialog.executeScript(scriptToRun.toString());
860
- }
861
- call.resolve();
862
- } else {
863
- call.reject("WebView is not initialized");
864
- }
865
- } catch (Exception e) {
866
- Log.e("InAppBrowser", "Error clearing cookies: " + e.getMessage());
867
- call.reject("Failed to clear cookies: " + e.getMessage());
868
- }
869
- }
870
- }
871
- );
872
- }
873
-
874
- @PluginMethod
875
- public void getCookies(PluginCall call) {
876
- String url = call.getString("url");
877
- if (url == null || TextUtils.isEmpty(url)) {
878
- call.reject("Invalid URL");
879
- return;
880
- }
881
- CookieManager cookieManager = CookieManager.getInstance();
882
- String cookieString = cookieManager.getCookie(url);
883
- JSObject result = new JSObject();
884
- if (cookieString != null) {
885
- String[] cookiePairs = cookieString.split("; ");
886
- for (String cookie : cookiePairs) {
887
- String[] parts = cookie.split("=", 2);
888
- if (parts.length == 2) {
889
- result.put(parts[0], parts[1]);
890
- }
891
- }
892
- }
893
- call.resolve(result);
894
- }
895
-
896
- @PluginMethod
897
- public void openWebView(PluginCall call) {
898
- String url = call.getString("url");
899
- if (url == null || TextUtils.isEmpty(url)) {
900
- call.reject("Invalid URL");
901
- }
902
- currentUrl = url;
903
- final String webViewId = UUID.randomUUID().toString();
904
- final Options options = new Options();
905
- options.setUrl(url);
906
- options.setHeaders(call.getObject("headers"));
907
- options.setCredentials(call.getObject("credentials"));
908
- options.setShowReloadButton(Boolean.TRUE.equals(call.getBoolean("showReloadButton", false)));
909
- options.setVisibleTitle(Boolean.TRUE.equals(call.getBoolean("visibleTitle", true)));
910
- if (Boolean.TRUE.equals(options.getVisibleTitle())) {
911
- options.setTitle(call.getString("title", "New Window"));
912
- } else {
913
- options.setTitle(call.getString("title", ""));
914
- }
915
- options.setToolbarColor(call.getString("toolbarColor", "#ffffff"));
916
- options.setBackgroundColor(call.getString("backgroundColor", "white"));
917
- options.setToolbarTextColor(call.getString("toolbarTextColor"));
918
- options.setArrow(Boolean.TRUE.equals(call.getBoolean("showArrow", false)));
919
- options.setIgnoreUntrustedSSLError(Boolean.TRUE.equals(call.getBoolean("ignoreUntrustedSSLError", false)));
920
- options.setShowScreenshotButton(Boolean.TRUE.equals(call.getBoolean("showScreenshotButton", false)));
921
- options.setAllowScreenshotsFromWebPage(Boolean.TRUE.equals(call.getBoolean("allowScreenshotsFromWebPage", false)));
922
- options.setCaptureConsoleLogs(Boolean.TRUE.equals(call.getBoolean("captureConsoleLogs", false)));
923
- options.setHandleDownloads(Boolean.TRUE.equals(call.getBoolean("handleDownloads", false)));
924
-
925
- // Set text zoom if specified in options (default is 100)
926
- Integer textZoom = call.getInt("textZoom");
927
- if (textZoom != null) {
928
- options.setTextZoom(textZoom);
929
- }
930
-
931
- options.setProxyRequests(Boolean.TRUE.equals(call.getBoolean("proxyRequests", false)));
932
-
933
- Object rawProxyRequests = call.getData().opt("proxyRequests");
934
- if (rawProxyRequests instanceof String proxyRequestsStr) {
935
- try {
936
- Pattern proxyRequestsPattern = ProxyRequestSupport.compileProxyRequestsPattern(rawProxyRequests);
937
- if (proxyRequestsPattern != null) {
938
- options.setProxyRequestsPattern(proxyRequestsPattern);
939
- }
940
- } catch (PatternSyntaxException e) {
941
- Log.e("WebViewDialog", String.format("Pattern '%s' is not a valid pattern", proxyRequestsStr));
942
- }
943
- }
944
-
945
- List<NativeProxyRule> outboundProxyRules = parseProxyRules(call.getArray("outboundProxyRules"), "outboundProxyRules", call);
946
- if (outboundProxyRules == null) {
947
- return;
948
- }
949
- options.setOutboundProxyRules(outboundProxyRules);
950
-
951
- List<NativeProxyRule> inboundProxyRules = parseProxyRules(call.getArray("inboundProxyRules"), "inboundProxyRules", call);
952
- if (inboundProxyRules == null) {
953
- return;
954
- }
955
- options.setInboundProxyRules(inboundProxyRules);
956
-
957
- JSObject buttonNearDoneObj = call.getObject("buttonNearDone");
958
- try {
959
- // Try to set buttonNearDone if present, with better error handling
960
- if (buttonNearDoneObj != null) {
961
- try {
962
- // Provide better debugging for buttonNearDone
963
- JSObject androidObj = buttonNearDoneObj.getJSObject("android");
964
- if (androidObj != null) {
965
- String iconType = androidObj.getString("iconType", "asset");
966
- String icon = androidObj.getString("icon", "");
967
- Log.d("InAppBrowser", "ButtonNearDone config - iconType: " + iconType + ", icon: " + icon);
968
-
969
- // For vector type, verify if resource exists
970
- if ("vector".equals(iconType)) {
971
- int resourceId = getContext().getResources().getIdentifier(icon, "drawable", getContext().getPackageName());
972
- if (resourceId == 0) {
973
- Log.e("InAppBrowser", "Vector resource not found: " + icon);
974
- call.reject("buttonNearDone validation failed: vector resource not found: " + icon);
975
- return;
976
- } else {
977
- Log.d("InAppBrowser", "Vector resource found with ID: " + resourceId);
978
- }
979
- }
980
- }
981
-
982
- // Try to create the ButtonNearDone object
983
- Options.ButtonNearDone buttonNearDone = Options.ButtonNearDone.generateFromPluginCall(call, getContext().getAssets());
984
- options.setButtonNearDone(buttonNearDone);
985
- } catch (Exception e) {
986
- Log.e("InAppBrowser", "Error setting buttonNearDone: " + e.getMessage(), e);
987
- call.reject("buttonNearDone validation failed: " + e.getMessage());
988
- return;
989
- }
990
- }
991
- } catch (Exception e) {
992
- Log.e("InAppBrowser", "Error processing buttonNearDone: " + e.getMessage(), e);
993
- call.reject("buttonNearDone validation failed: " + e.getMessage());
994
- return;
995
- }
996
-
997
- options.setShareDisclaimer(call.getObject("shareDisclaimer", null));
998
- options.setPreShowScript(call.getString("preShowScript", null));
999
- options.setShareSubject(call.getString("shareSubject", null));
1000
- options.setToolbarType(call.getString("toolbarType", ""));
1001
- options.setPreventDeeplink(Boolean.TRUE.equals(call.getBoolean("preventDeeplink", false)));
1002
- options.setOpenBlankTargetInWebView(Boolean.TRUE.equals(call.getBoolean("openBlankTargetInWebView", false)));
1003
-
1004
- // Validate preShowScript requires isPresentAfterPageLoad
1005
- if (call.getData().has("preShowScript") && !Boolean.TRUE.equals(call.getBoolean("isPresentAfterPageLoad", false))) {
1006
- call.reject("preShowScript requires isPresentAfterPageLoad to be true");
1007
- return;
1008
- }
1009
-
1010
- // Validate closeModal options
1011
- if (Boolean.TRUE.equals(call.getBoolean("closeModal", false))) {
1012
- options.setCloseModal(true);
1013
- options.setCloseModalTitle(call.getString("closeModalTitle", "Close"));
1014
- options.setCloseModalDescription(call.getString("closeModalDescription", "Are you sure ?"));
1015
- options.setCloseModalOk(call.getString("closeModalOk", "Ok"));
1016
- options.setCloseModalCancel(call.getString("closeModalCancel", "Cancel"));
1017
- String closeModalURLPatternStr = call.getString("closeModalURLPattern");
1018
- if (closeModalURLPatternStr != null) {
1019
- try {
1020
- options.setCloseModalURLPattern(Pattern.compile(closeModalURLPatternStr));
1021
- } catch (PatternSyntaxException e) {
1022
- call.reject("Invalid closeModalURLPattern regex: " + e.getMessage());
1023
- return;
1024
- }
1025
- }
1026
- } else {
1027
- // Reject if closeModal is false but closeModal options are provided
1028
- if (
1029
- call.getData().has("closeModalTitle") ||
1030
- call.getData().has("closeModalDescription") ||
1031
- call.getData().has("closeModalOk") ||
1032
- call.getData().has("closeModalCancel") ||
1033
- call.getData().has("closeModalURLPattern")
1034
- ) {
1035
- call.reject("closeModal options require closeModal to be true");
1036
- return;
1037
- }
1038
- options.setCloseModal(false);
1039
- }
1040
-
1041
- // Validate shareDisclaimer requires shareSubject
1042
- if (call.getData().has("shareDisclaimer") && !call.getData().has("shareSubject")) {
1043
- call.reject("shareDisclaimer requires shareSubject to be provided");
1044
- return;
1045
- }
1046
-
1047
- // Validate buttonNearDone compatibility with toolbar type
1048
- if (buttonNearDoneObj != null && options.getShowScreenshotButton()) {
1049
- call.reject("showScreenshotButton is not compatible with buttonNearDone");
1050
- return;
1051
- }
1052
-
1053
- if (buttonNearDoneObj != null || options.getShowScreenshotButton()) {
1054
- String toolbarType = options.getToolbarType();
1055
- if (
1056
- TextUtils.equals(toolbarType, "activity") ||
1057
- TextUtils.equals(toolbarType, "navigation") ||
1058
- TextUtils.equals(toolbarType, "blank")
1059
- ) {
1060
- String optionName = options.getShowScreenshotButton() ? "showScreenshotButton" : "buttonNearDone";
1061
- call.reject(optionName + " is not compatible with toolbarType: " + toolbarType);
1062
- return;
1063
- }
1064
- }
1065
-
1066
- options.setActiveNativeNavigationForWebview(Boolean.TRUE.equals(call.getBoolean("activeNativeNavigationForWebview", false)));
1067
- options.setDisableGoBackOnNativeApplication(Boolean.TRUE.equals(call.getBoolean("disableGoBackOnNativeApplication", false)));
1068
- options.setPresentAfterPageLoad(Boolean.TRUE.equals(call.getBoolean("isPresentAfterPageLoad", false)));
1069
- options.setPluginCall(call);
1070
-
1071
- // Set Material Design picker option
1072
- options.setMaterialPicker(Boolean.TRUE.equals(call.getBoolean("materialPicker", false)));
1073
-
1074
- // Set enabledSafeBottomMargin option
1075
- options.setEnabledSafeMargin(Boolean.TRUE.equals(call.getBoolean("enabledSafeBottomMargin", false)));
1076
-
1077
- // Set enabledSafeTopMargin option (defaults to true for safe area)
1078
- options.setEnabledSafeTopMargin(call.getBoolean("enabledSafeTopMargin", true));
1079
-
1080
- // Use system top inset for WebView margin when explicitly enabled
1081
- options.setUseTopInset(Boolean.TRUE.equals(call.getBoolean("useTopInset", false)));
1082
-
1083
- // options.getToolbarItemTypes().add(ToolbarItemType.RELOAD); TODO: fix this
1084
- options.setCallbacks(buildCallbacks(webViewId));
1085
-
1086
- JSArray jsAuthorizedLinks = call.getArray("authorizedAppLinks");
1087
- if (jsAuthorizedLinks != null && jsAuthorizedLinks.length() > 0) {
1088
- List<String> authorizedLinks = new ArrayList<>();
1089
- for (int i = 0; i < jsAuthorizedLinks.length(); i++) {
1090
- try {
1091
- String link = jsAuthorizedLinks.getString(i);
1092
- if (link != null && !link.trim().isEmpty()) {
1093
- authorizedLinks.add(link);
1094
- }
1095
- } catch (Exception e) {
1096
- Log.w("InAppBrowserPlugin", "Error reading authorized app link at index " + i, e);
1097
- }
1098
- }
1099
- Log.d("InAppBrowserPlugin", "Parsed authorized app links: " + authorizedLinks);
1100
- options.setAuthorizedAppLinks(authorizedLinks);
1101
- } else {
1102
- Log.d("InAppBrowserPlugin", "No authorized app links provided.");
1103
- }
1104
-
1105
- JSArray blockedHostsRaw = call.getArray("blockedHosts");
1106
- if (blockedHostsRaw != null && blockedHostsRaw.length() > 0) {
1107
- List<String> blockedHosts = new ArrayList<>();
1108
- for (int i = 0; i < blockedHostsRaw.length(); i++) {
1109
- try {
1110
- String host = blockedHostsRaw.getString(i);
1111
- if (host != null && !host.trim().isEmpty()) {
1112
- blockedHosts.add(host);
1113
- }
1114
- } catch (Exception e) {
1115
- Log.w("InAppBrowserPlugin", "Error reading blocked host at index " + i, e);
1116
- }
1117
- }
1118
- Log.d("InAppBrowserPlugin", "Parsed blocked hosts: " + blockedHosts);
1119
- options.setBlockedHosts(blockedHosts);
1120
- } else {
1121
- Log.d("InAppBrowserPlugin", "No blocked hosts provided.");
1122
- }
1123
-
1124
- // Set Google Pay support option
1125
- options.setEnableGooglePaySupport(Boolean.TRUE.equals(call.getBoolean("enableGooglePaySupport", false)));
1126
- options.setHiddenPopupWindow(Boolean.TRUE.equals(call.getBoolean("hiddenPopupWindow", false)));
1127
- options.setEnableZoom(Boolean.TRUE.equals(call.getBoolean("enableZoom", false)));
1128
- options.setHiddenPopupWindow(Boolean.TRUE.equals(call.getBoolean("hiddenPopupWindow", false)));
1129
-
1130
- // Set dimensions if provided
1131
- Integer width = call.getInt("width");
1132
- Integer height = call.getInt("height");
1133
- Integer x = call.getInt("x");
1134
- Integer y = call.getInt("y");
1135
-
1136
- // Validate dimension parameters
1137
- if (width != null && height == null) {
1138
- call.reject("Height must be specified when width is provided");
1139
- return;
1140
- }
1141
-
1142
- if (width != null) {
1143
- options.setWidth(width);
1144
- }
1145
- if (height != null) {
1146
- options.setHeight(height);
1147
- }
1148
- if (x != null) {
1149
- options.setX(x);
1150
- }
1151
- if (y != null) {
1152
- options.setY(y);
1153
- }
1154
-
1155
- options.setHidden(Boolean.TRUE.equals(call.getBoolean("hidden", false)));
1156
- boolean allowWebViewJsVisibilityControl = getConfig().getBoolean("allowWebViewJsVisibilityControl", false);
1157
- options.setAllowWebViewJsVisibilityControl(allowWebViewJsVisibilityControl);
1158
- options.setInvisibilityMode(Options.InvisibilityMode.fromString(call.getString("invisibilityMode", "AWARE")));
1159
-
1160
- // Set HTTP method and body if provided
1161
- String httpMethod = call.getString("method");
1162
- String httpBody = call.getString("body");
1163
- if (httpMethod != null) {
1164
- options.setHttpMethod(httpMethod);
1165
- }
1166
- if (httpBody != null) {
1167
- options.setHttpBody(httpBody);
1168
- }
1169
-
1170
- this.getActivity().runOnUiThread(
1171
- new Runnable() {
1172
- @Override
1173
- public void run() {
1174
- WebViewDialog dialog = createManagedDialog(webViewId, options);
1175
- dialog.presentWebView();
1176
- if (options.isHidden()) {
1177
- dialog.setHidden(true);
1178
- }
1179
- }
1180
- }
1181
- );
1182
- }
1183
-
1184
- @NonNull
1185
- private static StringBuilder getStringBuilder() {
1186
- Field[] drawables = R.drawable.class.getFields();
1187
- StringBuilder availableResources = new StringBuilder("Available resources: ");
1188
- for (int i = 0; i < Math.min(10, drawables.length); i++) {
1189
- availableResources.append(drawables[i].getName()).append(", ");
1190
- }
1191
- if (drawables.length > 10) {
1192
- availableResources.append("... (").append(drawables.length - 10).append(" more)");
1193
- }
1194
- return availableResources;
1195
- }
1196
-
1197
- @PluginMethod
1198
- public void postMessage(PluginCall call) {
1199
- JSObject eventData = call.getObject("detail");
1200
- // Log event data
1201
- if (eventData == null) {
1202
- call.reject("No event data provided");
1203
- return;
1204
- }
1205
-
1206
- String targetId = call.getString("id");
1207
- Log.d("InAppBrowserPlugin", "Event data: " + eventData.toString());
1208
- this.getActivity().runOnUiThread(
1209
- new Runnable() {
1210
- @Override
1211
- public void run() {
1212
- if (targetId != null) {
1213
- WebViewDialog dialog = webViewDialogs.get(targetId);
1214
- if (dialog == null) {
1215
- call.reject("WebView is not initialized");
1216
- return;
1217
- }
1218
- dialog.postMessageToJS(eventData);
1219
- call.resolve();
1220
- return;
1221
- }
1222
-
1223
- if (!webViewDialogs.isEmpty()) {
1224
- for (WebViewDialog dialog : webViewDialogs.values()) {
1225
- dialog.postMessageToJS(eventData);
1226
- }
1227
- call.resolve();
1228
- return;
1229
- }
1230
-
1231
- // TODO: Legacy, can be removed(?)
1232
- if (webViewDialog != null) {
1233
- webViewDialog.postMessageToJS(eventData);
1234
- call.resolve();
1235
- } else {
1236
- call.reject("WebView is not initialized");
1237
- }
1238
- }
1239
- }
1240
- );
1241
- }
1242
-
1243
- @PluginMethod
1244
- public void hide(PluginCall call) {
1245
- String targetId = resolveTargetId(call);
1246
- WebViewDialog targetDialog = resolveDialog(targetId);
1247
- if (targetDialog == null) {
1248
- call.reject("WebView is not initialized");
1249
- return;
1250
- }
1251
-
1252
- this.getActivity().runOnUiThread(
1253
- new Runnable() {
1254
- @Override
1255
- public void run() {
1256
- WebViewDialog dialog = resolveDialog(targetId);
1257
- if (dialog == null) {
1258
- call.reject("WebView is not initialized");
1259
- return;
1260
- }
1261
- dialog.setHidden(true);
1262
- call.resolve();
1263
- }
1264
- }
1265
- );
1266
- }
1267
-
1268
- @PluginMethod
1269
- public void show(PluginCall call) {
1270
- String targetId = resolveTargetId(call);
1271
- WebViewDialog targetDialog = resolveDialog(targetId);
1272
- if (targetDialog == null) {
1273
- call.reject("WebView is not initialized");
1274
- return;
1275
- }
1276
-
1277
- this.getActivity().runOnUiThread(
1278
- new Runnable() {
1279
- @Override
1280
- public void run() {
1281
- WebViewDialog dialog = resolveDialog(targetId);
1282
- if (dialog == null) {
1283
- call.reject("WebView is not initialized");
1284
- return;
1285
- }
1286
- if (!dialog.isShowing()) {
1287
- dialog.show();
1288
- }
1289
- dialog.setHidden(false);
1290
- if (dialog.getInstanceId() != null) {
1291
- setActiveWebView(dialog.getInstanceId(), dialog);
1292
- }
1293
- call.resolve();
1294
- }
1295
- }
1296
- );
1297
- }
1298
-
1299
- @PluginMethod
1300
- public void executeScript(PluginCall call) {
1301
- String script = call.getString("code");
1302
- if (script == null || script.trim().isEmpty()) {
1303
- call.reject("Script is required");
1304
- return;
1305
- }
1306
-
1307
- String targetId = call.getString("id");
1308
- this.getActivity().runOnUiThread(
1309
- new Runnable() {
1310
- @Override
1311
- public void run() {
1312
- try {
1313
- if (targetId != null) {
1314
- WebViewDialog dialog = webViewDialogs.get(targetId);
1315
- if (dialog == null) {
1316
- call.reject("WebView is not initialized");
1317
- return;
1318
- }
1319
- dialog.executeScript(script);
1320
- call.resolve();
1321
- return;
1322
- }
1323
-
1324
- if (!webViewDialogs.isEmpty()) {
1325
- for (WebViewDialog dialog : webViewDialogs.values()) {
1326
- dialog.executeScript(script);
1327
- }
1328
- call.resolve();
1329
- return;
1330
- }
1331
-
1332
- // TODO: Legacy, can be removed(?)
1333
- if (webViewDialog != null) {
1334
- webViewDialog.executeScript(script);
1335
- call.resolve();
1336
- } else {
1337
- call.reject("WebView is not initialized");
1338
- }
1339
- } catch (Exception e) {
1340
- Log.e("InAppBrowser", "Error executing script: " + e.getMessage());
1341
- call.reject("Failed to execute script: " + e.getMessage());
1342
- }
1343
- }
1344
- }
1345
- );
1346
- }
1347
-
1348
- @PluginMethod
1349
- public void takeScreenshot(PluginCall call) {
1350
- this.getActivity().runOnUiThread(
1351
- new Runnable() {
1352
- @Override
1353
- public void run() {
1354
- String targetId = resolveTargetId(call);
1355
- WebViewDialog dialog = resolveDialog(targetId);
1356
- if (dialog == null) {
1357
- call.reject("WebView is not initialized");
1358
- return;
1359
- }
1360
-
1361
- dialog.takeScreenshot(
1362
- new WebViewDialog.ScreenshotResultCallback() {
1363
- @Override
1364
- public void onSuccess(JSObject screenshot) {
1365
- call.resolve(screenshot);
1366
- }
1367
-
1368
- @Override
1369
- public void onError(String message) {
1370
- call.reject(message);
1371
- }
1372
- }
1373
- );
1374
- }
1375
- }
1376
- );
1377
- }
1378
-
1379
- @PluginMethod
1380
- public void goBack(PluginCall call) {
1381
- this.getActivity().runOnUiThread(
1382
- new Runnable() {
1383
- @Override
1384
- public void run() {
1385
- String targetId = resolveTargetId(call);
1386
- WebViewDialog webViewDialog = resolveDialog(targetId);
1387
- if (webViewDialog != null) {
1388
- boolean canGoBack = webViewDialog.goBack();
1389
- JSObject result = new JSObject();
1390
- result.put("canGoBack", canGoBack);
1391
- call.resolve(result);
1392
- } else {
1393
- JSObject result = new JSObject();
1394
- result.put("canGoBack", false);
1395
- call.resolve(result);
1396
- }
1397
- }
1398
- }
1399
- );
1400
- }
1401
-
1402
- @PluginMethod
1403
- public void reload(PluginCall call) {
1404
- this.getActivity().runOnUiThread(
1405
- new Runnable() {
1406
- @Override
1407
- public void run() {
1408
- String targetId = resolveTargetId(call);
1409
- WebViewDialog webViewDialog = resolveDialog(targetId);
1410
- if (webViewDialog != null) {
1411
- webViewDialog.reload();
1412
- call.resolve();
1413
- } else {
1414
- call.reject("WebView is not initialized");
1415
- }
1416
- }
1417
- }
1418
- );
1419
- }
1420
-
1421
- @PluginMethod
1422
- public void lsuakdchgbbaHandleProxiedRequest(PluginCall call) {
1423
- String requestId = call.getString("id");
1424
- if (requestId == null) {
1425
- Log.e("InAppBrowserProxy", "CRITICAL ERROR, proxy id = null");
1426
- call.resolve();
1427
- return;
1428
- }
1429
- JSObject decision = null;
1430
- if (Boolean.TRUE.equals(call.getBoolean("ok", false))) {
1431
- JSONObject object = call.getObject("result");
1432
- if (object != null) {
1433
- decision = new JSObject();
1434
- decision.put("response", ProxyRequestSupport.normalizeLegacySyntheticResponse(object));
1435
- }
1436
- }
1437
- handleProxyRequestInternal(call.getString("webviewId"), requestId, decision, call);
1438
- }
1439
-
1440
- @PluginMethod
1441
- public void handleProxyRequest(PluginCall call) {
1442
- String requestId = call.getString("requestId");
1443
- if (requestId == null) {
1444
- call.reject("requestId is required");
1445
- return;
1446
- }
1447
-
1448
- JSObject decision = call.getObject("decision");
1449
- if (decision == null) {
1450
- decision = call.getObject("response");
1451
- }
1452
-
1453
- handleProxyRequestInternal(call.getString("webviewId"), requestId, decision, call);
1454
- }
1455
-
1456
- private void handleProxyRequestInternal(String webviewId, String requestId, JSObject decision, PluginCall call) {
1457
- WebViewDialog dialog = ProxyResponseRouting.resolveTargetDialog(webviewId, requestId, webViewDialogs);
1458
- if (dialog == null) {
1459
- if (webviewId == null || webviewId.isBlank()) {
1460
- call.reject("webviewId is required when the proxy request cannot be mapped to a unique WebView");
1461
- return;
1462
- }
1463
- call.reject("Target WebView not found for proxy request");
1464
- return;
1465
- }
1466
-
1467
- dialog.handleProxyResponse(requestId, decision);
1468
- call.resolve();
1469
- }
1470
-
1471
- @PluginMethod
1472
- public void close(PluginCall call) {
1473
- String targetId = resolveTargetId(call);
1474
- WebViewDialog dialog = resolveDialog(targetId);
1475
- if (dialog == null) {
1476
- // Fallback: try to bring main activity to foreground
1477
- try {
1478
- Intent intent = new Intent(getContext(), getBridge().getActivity().getClass());
1479
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1480
- getContext().startActivity(intent);
1481
- call.resolve();
1482
- } catch (Exception e) {
1483
- Log.e("InAppBrowser", "Error bringing main activity to foreground: " + e.getMessage());
1484
- call.reject("WebView is not initialized and failed to restore main activity");
1485
- }
1486
- return;
1487
- }
1488
-
1489
- this.getActivity().runOnUiThread(
1490
- new Runnable() {
1491
- @Override
1492
- public void run() {
1493
- try {
1494
- if (dialog != null) {
1495
- String currentUrl = "";
1496
- try {
1497
- currentUrl = dialog.getUrl();
1498
- if (currentUrl == null) {
1499
- currentUrl = "";
1500
- }
1501
- } catch (Exception e) {
1502
- Log.e("InAppBrowser", "Error getting URL before close: " + e.getMessage());
1503
- currentUrl = "";
1504
- }
1505
-
1506
- // Notify listeners about the close event
1507
- JSObject eventData = new JSObject().put("url", currentUrl);
1508
- if (targetId != null) {
1509
- eventData.put("id", targetId);
1510
- }
1511
- notifyListeners("closeEvent", eventData);
1512
-
1513
- dialog.dismiss();
1514
- if (targetId != null) {
1515
- unregisterWebView(targetId);
1516
- } else {
1517
- webViewDialog = null;
1518
- }
1519
- call.resolve();
1520
- } else {
1521
- // Secondary fallback inside UI thread
1522
- try {
1523
- Intent intent = new Intent(getContext(), getBridge().getActivity().getClass());
1524
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1525
- getContext().startActivity(intent);
1526
- call.resolve();
1527
- } catch (Exception e) {
1528
- Log.e("InAppBrowser", "Error in secondary fallback: " + e.getMessage());
1529
- call.reject("WebView is not initialized");
1530
- }
1531
- }
1532
- } catch (Exception e) {
1533
- Log.e("InAppBrowser", "Error closing WebView: " + e.getMessage());
1534
- call.reject("Failed to close WebView: " + e.getMessage());
1535
- }
1536
- }
1537
- }
1538
- );
1539
- }
1540
-
1541
- private Bundle getHeaders(PluginCall pluginCall) {
1542
- JSObject headersProvided = pluginCall.getObject("headers");
1543
- Bundle headers = new Bundle();
1544
- if (headersProvided != null) {
1545
- Iterator<String> keys = headersProvided.keys();
1546
- while (keys.hasNext()) {
1547
- String key = keys.next();
1548
- headers.putString(key, headersProvided.getString(key));
1549
- }
1550
- }
1551
- return headers;
1552
- }
1553
-
1554
- protected void handleOnResume() {
1555
- boolean ok = CustomTabsClient.bindCustomTabsService(getContext(), CUSTOM_TAB_PACKAGE_NAME, connection);
1556
- if (!ok) {
1557
- Log.e(getLogTag(), "Error binding to custom tabs service");
1558
- }
1559
- // If we have a saved call and user returned without callback, reject
1560
- if (openSecureWindowSavedCall != null) {
1561
- openSecureWindowSavedCall.reject("OAuth cancelled or no callback received");
1562
- openSecureWindowSavedCall = null;
1563
- }
1564
- }
1565
-
1566
- protected void handleOnPause() {
1567
- getContext().unbindService(connection);
1568
- }
1569
-
1570
- public CustomTabsSession getCustomTabsSession() {
1571
- if (customTabsClient == null) {
1572
- return null;
1573
- }
1574
-
1575
- if (currentSession == null) {
1576
- currentSession = customTabsClient.newSession(
1577
- new CustomTabsCallback() {
1578
- @Override
1579
- public void onNavigationEvent(int navigationEvent, Bundle extras) {
1580
- // Only fire browserPageLoaded for Custom Tabs, not for WebView
1581
- if (navigationEvent == NAVIGATION_FINISHED && webViewDialogs.isEmpty() && webViewDialog == null) {
1582
- notifyListeners("browserPageLoaded", new JSObject());
1583
- }
1584
- }
1585
- }
1586
- );
1587
- }
1588
- return currentSession;
1589
- }
1590
-
1591
- @Override
1592
- protected void handleOnDestroy() {
1593
- for (WebViewDialog dialog : webViewDialogs.values()) {
1594
- try {
1595
- dialog.dismiss();
1596
- } catch (Exception e) {
1597
- // Ignore, dialog may already be dismissed
1598
- }
1599
- }
1600
- webViewDialogs.clear();
1601
- webViewStack.clear();
1602
- activeWebViewId = null;
1603
- webViewDialog = null;
1604
- clearCurrentWebPermissionRequest();
1605
- if (pendingCameraPermissionCall != null) {
1606
- pendingCameraPermissionCall.reject("Plugin destroyed before permission response");
1607
- pendingCameraPermissionCall = null;
1608
- }
1609
- customTabsClient = null;
1610
- currentSession = null;
1611
- openSecureWindowSavedCall = null;
1612
- super.handleOnDestroy();
1613
- }
1614
-
1615
- @PluginMethod
1616
- public void getPluginVersion(final PluginCall call) {
1617
- try {
1618
- final JSObject ret = new JSObject();
1619
- ret.put("version", this.pluginVersion);
1620
- call.resolve(ret);
1621
- } catch (final Exception e) {
1622
- call.reject("Could not get plugin version", e);
1623
- }
1624
- }
1625
-
1626
- @PluginMethod
1627
- public void updateDimensions(PluginCall call) {
1628
- String targetId = resolveTargetId(call);
1629
- WebViewDialog webViewDialog = resolveDialog(targetId);
1630
- if (webViewDialog == null) {
1631
- call.reject("WebView is not initialized");
1632
- return;
1633
- }
1634
-
1635
- Integer width = call.getInt("width");
1636
- Integer height = call.getInt("height");
1637
- Integer x = call.getInt("x");
1638
- Integer y = call.getInt("y");
1639
-
1640
- this.getActivity().runOnUiThread(
1641
- new Runnable() {
1642
- @Override
1643
- public void run() {
1644
- try {
1645
- if (webViewDialog != null) {
1646
- webViewDialog.updateDimensions(width, height, x, y);
1647
- call.resolve();
1648
- } else {
1649
- call.reject("WebView is not initialized");
1650
- }
1651
- } catch (Exception e) {
1652
- Log.e("InAppBrowser", "Error updating dimensions: " + e.getMessage());
1653
- call.reject("Failed to update dimensions: " + e.getMessage());
1654
- }
1655
- }
1656
- }
1657
- );
1658
- }
1659
-
1660
- @PluginMethod
1661
- public void setEnabledSafeTopMargin(PluginCall call) {
1662
- Boolean enabledValue = call.getBoolean("enabled");
1663
- if (enabledValue == null) {
1664
- call.reject("'enabled' (boolean) is required");
1665
- return;
1666
- }
1667
- boolean enabled = enabledValue;
1668
- String targetId = resolveTargetId(call);
1669
- WebViewDialog dialog = resolveDialog(targetId);
1670
- if (dialog == null) {
1671
- call.reject("WebView is not initialized");
1672
- return;
1673
- }
1674
- this.getActivity().runOnUiThread(() -> {
1675
- try {
1676
- dialog.setEnabledSafeTopMargin(enabled);
1677
- call.resolve();
1678
- } catch (Exception e) {
1679
- Log.e("InAppBrowser", "Error setting safe top margin: " + e.getMessage());
1680
- call.reject("Failed to set safe top margin: " + e.getMessage());
1681
- }
1682
- });
1683
- }
1684
-
1685
- @PluginMethod
1686
- public void setEnabledSafeBottomMargin(PluginCall call) {
1687
- Boolean enabledValue = call.getBoolean("enabled");
1688
- if (enabledValue == null) {
1689
- call.reject("'enabled' (boolean) is required");
1690
- return;
1691
- }
1692
- boolean enabled = enabledValue;
1693
- String targetId = resolveTargetId(call);
1694
- WebViewDialog dialog = resolveDialog(targetId);
1695
- if (dialog == null) {
1696
- call.reject("WebView is not initialized");
1697
- return;
1698
- }
1699
- this.getActivity().runOnUiThread(() -> {
1700
- try {
1701
- dialog.setEnabledSafeBottomMargin(enabled);
1702
- call.resolve();
1703
- } catch (Exception e) {
1704
- Log.e("InAppBrowser", "Error setting safe bottom margin: " + e.getMessage());
1705
- call.reject("Failed to set safe bottom margin: " + e.getMessage());
1706
- }
1707
- });
1708
- }
1709
-
1710
- @PluginMethod
1711
- public void openSecureWindow(PluginCall call) {
1712
- String authEndpoint = call.getString("authEndpoint");
1713
-
1714
- if (authEndpoint == null || authEndpoint.isEmpty()) {
1715
- call.reject("Auth endpoint is required");
1716
- return;
1717
- }
1718
-
1719
- String redirectUri = call.getString("redirectUri");
1720
- if (redirectUri == null || redirectUri.isEmpty()) {
1721
- call.reject("Redirect URI is required");
1722
- return;
1723
- }
1724
-
1725
- openSecureWindowSavedCall = call;
1726
- openSecureWindowRedirectUri = redirectUri;
1727
-
1728
- CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
1729
- builder.enableUrlBarHiding();
1730
- builder.setShareState(CustomTabsIntent.SHARE_STATE_OFF);
1731
- builder.setEphemeralBrowsingEnabled(Boolean.TRUE.equals(call.getBoolean("prefersEphemeralWebBrowserSession", false)));
1732
- CustomTabsIntent customTabsIntent = builder.build();
1733
- customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1734
- customTabsIntent.intent.putExtra(CustomTabsIntent.EXTRA_ENABLE_INSTANT_APPS, false);
1735
- customTabsIntent.intent.putExtra(CustomTabsIntent.EXTRA_DISABLE_BACKGROUND_INTERACTION, false);
1736
- customTabsIntent.launchUrl(getActivity(), Uri.parse(authEndpoint));
1737
- }
1738
-
1739
- @Override
1740
- protected void handleOnNewIntent(Intent intent) {
1741
- super.handleOnNewIntent(intent);
1742
-
1743
- if (intent == null || !Intent.ACTION_VIEW.equals(intent.getAction())) {
1744
- return;
1745
- }
1746
-
1747
- Uri uri = intent.getData();
1748
- if (uri == null) {
1749
- return;
1750
- }
1751
-
1752
- if (openSecureWindowRedirectUri == null) {
1753
- return;
1754
- }
1755
-
1756
- if (uri.getHost() == null || !uri.toString().startsWith(openSecureWindowRedirectUri)) {
1757
- return;
1758
- }
1759
-
1760
- try {
1761
- // Resolve the original call with the callback url
1762
- if (openSecureWindowSavedCall != null) {
1763
- final JSObject ret = new JSObject();
1764
- ret.put("redirectedUri", uri.toString());
1765
- openSecureWindowSavedCall.resolve(ret);
1766
- openSecureWindowSavedCall = null;
1767
- }
1768
- } catch (Exception e) {
1769
- if (openSecureWindowSavedCall != null) {
1770
- openSecureWindowSavedCall.reject("Failed to process OAuth callback", e);
1771
- openSecureWindowSavedCall = null;
1772
- }
1773
- }
1774
- }
1775
- }
1776
-
1777
- final class ConsoleMessageEventHelper {
1778
-
1779
- private ConsoleMessageEventHelper() {}
1780
-
1781
- static JSObject create(String webViewId, String level, String message, String source, Integer line, Integer column) {
1782
- Map<String, Object> data = createData(webViewId, level, message, source, line, column);
1783
- JSObject event = new JSObject();
1784
- for (Map.Entry<String, Object> entry : data.entrySet()) {
1785
- event.put(entry.getKey(), entry.getValue());
1786
- }
1787
- return event;
1788
- }
1789
-
1790
- static Map<String, Object> createData(String webViewId, String level, String message, String source, Integer line, Integer column) {
1791
- Map<String, Object> event = new HashMap<>();
1792
- if (webViewId != null && !webViewId.isEmpty()) {
1793
- event.put("id", webViewId);
1794
- }
1795
- event.put("level", normalizeLevel(level));
1796
- event.put("message", message != null ? message : "");
1797
- if (source != null && !source.isEmpty()) {
1798
- event.put("source", source);
1799
- }
1800
- if (line != null) {
1801
- event.put("line", line);
1802
- }
1803
- if (column != null) {
1804
- event.put("column", column);
1805
- }
1806
- return event;
1807
- }
1808
-
1809
- static String normalizeLevel(String level) {
1810
- if (level == null) {
1811
- return "log";
1812
- }
1813
-
1814
- String normalized = level.trim().toLowerCase();
1815
- return switch (normalized) {
1816
- case "log", "info", "warn", "error", "debug", "assert" -> normalized;
1817
- default -> "log";
1818
- };
1819
- }
1820
- }
17
+ public class InAppBrowserPlugin extends CapgoInAppBrowserPlugin {}