@capgo/capacitor-inappbrowser 8.15.3 → 8.15.4

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.
@@ -20,7 +20,6 @@ import android.util.Log;
20
20
  import android.webkit.CookieManager;
21
21
  import android.webkit.PermissionRequest;
22
22
  import android.webkit.WebView;
23
- import androidx.activity.result.ActivityResult;
24
23
  import androidx.activity.result.ActivityResultLauncher;
25
24
  import androidx.activity.result.contract.ActivityResultContracts;
26
25
  import androidx.annotation.NonNull;
@@ -63,8 +62,7 @@ import org.json.JSONObject;
63
62
  @Permission(alias = "camera", strings = { Manifest.permission.CAMERA }),
64
63
  @Permission(alias = "microphone", strings = { Manifest.permission.RECORD_AUDIO }),
65
64
  @Permission(alias = "storage", strings = { Manifest.permission.READ_EXTERNAL_STORAGE })
66
- },
67
- requestCodes = { WebViewDialog.FILE_CHOOSER_REQUEST_CODE }
65
+ }
68
66
  )
69
67
  public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.PermissionHandler {
70
68
 
@@ -108,7 +106,6 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
108
106
  private boolean currentPermissionNeedsCamera = false;
109
107
  private boolean currentPermissionNeedsMicrophone = false;
110
108
 
111
- private ActivityResultLauncher<Intent> fileChooserLauncher;
112
109
  private ActivityResultLauncher<String[]> webPermissionLauncher;
113
110
  private PluginCall pendingCameraPermissionCall;
114
111
 
@@ -118,10 +115,6 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
118
115
  @Override
119
116
  public void load() {
120
117
  super.load();
121
- fileChooserLauncher = getActivity().registerForActivityResult(
122
- new ActivityResultContracts.StartActivityForResult(),
123
- this::handleFileChooserResult
124
- );
125
118
  webPermissionLauncher = getActivity().registerForActivityResult(
126
119
  new ActivityResultContracts.RequestMultiplePermissions(),
127
120
  this::handleWebPermissionResult
@@ -355,6 +348,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
355
348
  );
356
349
  dialog.setInstanceId(webViewId);
357
350
  dialog.activity = CapgoInAppBrowserPlugin.this.getActivity();
351
+ dialog.bindToHostActivity();
358
352
  registerWebView(webViewId, dialog, makeActive);
359
353
  return dialog;
360
354
  }
@@ -525,59 +519,6 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
525
519
  return targetWebViews;
526
520
  }
527
521
 
528
- private WebViewDialog findFileChooserDialog() {
529
- if (activeWebViewId != null) {
530
- WebViewDialog dialog = webViewDialogs.get(activeWebViewId);
531
- if (dialog != null && dialog.mFilePathCallback != null) {
532
- return dialog;
533
- }
534
- }
535
-
536
- if (webViewDialog != null && webViewDialog.mFilePathCallback != null) {
537
- return webViewDialog;
538
- }
539
-
540
- for (WebViewDialog dialog : webViewDialogs.values()) {
541
- if (dialog != null && dialog.mFilePathCallback != null) {
542
- return dialog;
543
- }
544
- }
545
-
546
- return null;
547
- }
548
-
549
- private void handleFileChooserResult(ActivityResult result) {
550
- WebViewDialog webViewDialog = findFileChooserDialog();
551
- if (webViewDialog != null && webViewDialog.mFilePathCallback != null) {
552
- Uri[] results = null;
553
- Intent data = result.getData();
554
-
555
- if (result.getResultCode() == Activity.RESULT_OK) {
556
- // Handle camera capture result
557
- if (webViewDialog.tempCameraUri != null && (data == null || data.getData() == null)) {
558
- results = new Uri[] { webViewDialog.tempCameraUri };
559
- }
560
- // Handle regular file picker result
561
- else if (data != null) {
562
- if (data.getClipData() != null) {
563
- // Handle multiple files
564
- int count = data.getClipData().getItemCount();
565
- results = new Uri[count];
566
- for (int i = 0; i < count; i++) {
567
- results[i] = data.getClipData().getItemAt(i).getUri();
568
- }
569
- } else if (data.getData() != null) {
570
- // Handle single file
571
- results = new Uri[] { data.getData() };
572
- }
573
- }
574
- }
575
-
576
- // Send the result to WebView and clean up
577
- webViewDialog.completeLegacyFileChooserResult(results);
578
- }
579
- }
580
-
581
522
  public void handleMicrophonePermissionRequest(PermissionRequest request) {
582
523
  if (request == null) {
583
524
  return;
@@ -796,6 +737,16 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
796
737
  );
797
738
  }
798
739
 
740
+ private CustomTabColorSchemeParams buildCustomTabColorScheme(int colorInt) {
741
+ return applyCustomTabNavigationBarColor(new CustomTabColorSchemeParams.Builder().setToolbarColor(colorInt), colorInt).build();
742
+ }
743
+
744
+ @SuppressWarnings("deprecation")
745
+ private CustomTabColorSchemeParams.Builder applyCustomTabNavigationBarColor(CustomTabColorSchemeParams.Builder builder, int colorInt) {
746
+ // androidx.browser 1.9.0 still exposes only setNavigationBarColor; no non-deprecated replacement.
747
+ return builder.setNavigationBarColor(colorInt);
748
+ }
749
+
799
750
  @PluginMethod
800
751
  public void open(PluginCall call) {
801
752
  String url = call.getString("url");
@@ -828,10 +779,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
828
779
  if (toolbarColor != null) {
829
780
  try {
830
781
  int colorInt = Color.parseColor(toolbarColor);
831
- CustomTabColorSchemeParams colorParams = new CustomTabColorSchemeParams.Builder()
832
- .setToolbarColor(colorInt)
833
- .setNavigationBarColor(colorInt)
834
- .build();
782
+ CustomTabColorSchemeParams colorParams = buildCustomTabColorScheme(colorInt);
835
783
  builder.setDefaultColorSchemeParams(colorParams);
836
784
  builder.setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, colorParams);
837
785
  } catch (IllegalArgumentException e) {
@@ -11,7 +11,6 @@ import com.getcapacitor.annotation.Permission;
11
11
  @Permission(alias = "camera", strings = { Manifest.permission.CAMERA }),
12
12
  @Permission(alias = "microphone", strings = { Manifest.permission.RECORD_AUDIO }),
13
13
  @Permission(alias = "storage", strings = { Manifest.permission.READ_EXTERNAL_STORAGE })
14
- },
15
- requestCodes = { WebViewDialog.FILE_CHOOSER_REQUEST_CODE }
14
+ }
16
15
  )
17
16
  public class InAppBrowserPlugin extends CapgoInAppBrowserPlugin {}
@@ -122,9 +122,14 @@ final class SafeAreaInsetsSupport {
122
122
  * Whether the WebView container must be inset for the bottom system bar. On Android 15+ the dialog
123
123
  * is forced edge-to-edge (decorFitsSystemWindows=false), so the window always draws under the
124
124
  * navigation bar and insetting is mandatory to keep bottom content on-screen, regardless of the
125
- * enabledSafeBottomMargin opt-in that older versions honour.
125
+ * enabledSafeBottomMargin opt-in that older versions honour. Pre-API 30 windows that lay out behind
126
+ * a transparent navigation bar need the same treatment.
126
127
  */
128
+ static boolean shouldInsetBottomForContainer(boolean enabledSafeBottomMargin, boolean isEdgeToEdge, boolean layoutBehindNavigationBar) {
129
+ return enabledSafeBottomMargin || isEdgeToEdge || layoutBehindNavigationBar;
130
+ }
131
+
127
132
  static boolean shouldInsetBottomForContainer(boolean enabledSafeBottomMargin, boolean isEdgeToEdge) {
128
- return enabledSafeBottomMargin || isEdgeToEdge;
133
+ return shouldInsetBottomForContainer(enabledSafeBottomMargin, isEdgeToEdge, false);
129
134
  }
130
135
  }
@@ -0,0 +1,109 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.graphics.Color;
4
+ import android.os.Build;
5
+ import android.view.View;
6
+ import android.view.Window;
7
+ import android.view.WindowManager;
8
+ import androidx.core.view.WindowCompat;
9
+ import androidx.core.view.WindowInsetsControllerCompat;
10
+
11
+ /**
12
+ * Centralizes window chrome decisions so WebViewDialog can avoid deprecated system UI APIs on
13
+ * Android 15+ while keeping API 24–34 behavior unchanged.
14
+ */
15
+ final class SystemUiChromeSupport {
16
+
17
+ static final int EDGE_TO_EDGE_SDK = Build.VERSION_CODES.VANILLA_ICE_CREAM;
18
+
19
+ private SystemUiChromeSupport() {}
20
+
21
+ static boolean requiresEdgeToEdgeChrome(int sdkInt) {
22
+ return sdkInt >= EDGE_TO_EDGE_SDK;
23
+ }
24
+
25
+ static boolean shouldApplyLegacySystemBarColors(int sdkInt) {
26
+ return sdkInt < EDGE_TO_EDGE_SDK;
27
+ }
28
+
29
+ static boolean shouldUsePreApi30LayoutFlags(int sdkInt) {
30
+ return sdkInt < Build.VERSION_CODES.R;
31
+ }
32
+
33
+ static boolean usesLayoutBehindNavigationBar(int sdkInt, boolean edgeToEdge) {
34
+ return !edgeToEdge && shouldUsePreApi30LayoutFlags(sdkInt);
35
+ }
36
+
37
+ static void setDecorFitsSystemWindows(Window window, boolean decorFitsSystemWindows) {
38
+ if (window == null) {
39
+ return;
40
+ }
41
+ WindowCompat.setDecorFitsSystemWindows(window, decorFitsSystemWindows);
42
+ }
43
+
44
+ static void prepareInitialDialogWindow(Window window) {
45
+ if (window == null) {
46
+ return;
47
+ }
48
+
49
+ window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
50
+ if (shouldApplyLegacySystemBarColors(Build.VERSION.SDK_INT)) {
51
+ applyLegacySystemBarColors(window, Color.TRANSPARENT, null);
52
+ clearLegacyTranslucentStatusFlag(window);
53
+ }
54
+ }
55
+
56
+ static void applyDialogWindowChrome(
57
+ Window window,
58
+ View decorView,
59
+ boolean edgeToEdge,
60
+ Integer statusBarColor,
61
+ Integer navigationBarColor,
62
+ Boolean lightStatusBars
63
+ ) {
64
+ if (window == null || decorView == null) {
65
+ return;
66
+ }
67
+
68
+ if (edgeToEdge) {
69
+ setDecorFitsSystemWindows(window, false);
70
+ } else if (!shouldUsePreApi30LayoutFlags(Build.VERSION.SDK_INT)) {
71
+ setDecorFitsSystemWindows(window, true);
72
+ applyLegacySystemBarColors(window, statusBarColor, navigationBarColor);
73
+ } else {
74
+ applyPreApi30LayoutBehindNavigationBar(decorView);
75
+ applyLegacySystemBarColors(window, statusBarColor, navigationBarColor);
76
+ }
77
+
78
+ WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(window, decorView);
79
+ if (lightStatusBars != null) {
80
+ controller.setAppearanceLightStatusBars(lightStatusBars);
81
+ }
82
+ }
83
+
84
+ @SuppressWarnings("deprecation")
85
+ private static void applyPreApi30LayoutBehindNavigationBar(View decorView) {
86
+ decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
87
+ }
88
+
89
+ @SuppressWarnings("deprecation")
90
+ static void applyLegacySystemBarColors(Window window, Integer statusBarColor, Integer navigationBarColor) {
91
+ if (window == null || !shouldApplyLegacySystemBarColors(Build.VERSION.SDK_INT)) {
92
+ return;
93
+ }
94
+
95
+ if (statusBarColor != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
96
+ window.setStatusBarColor(statusBarColor);
97
+ }
98
+ if (navigationBarColor != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
99
+ window.setNavigationBarColor(navigationBarColor);
100
+ }
101
+ }
102
+
103
+ @SuppressWarnings("deprecation")
104
+ private static void clearLegacyTranslucentStatusFlag(Window window) {
105
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
106
+ window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
107
+ }
108
+ }
109
+ }
@@ -69,7 +69,9 @@ import android.widget.RelativeLayout;
69
69
  import android.widget.TextView;
70
70
  import android.widget.Toast;
71
71
  import android.widget.Toolbar;
72
+ import androidx.activity.ComponentActivity;
72
73
  import androidx.activity.result.ActivityResult;
74
+ import androidx.activity.result.ActivityResultLauncher;
73
75
  import androidx.activity.result.contract.ActivityResultContracts;
74
76
  import androidx.coordinatorlayout.widget.CoordinatorLayout;
75
77
  import androidx.core.content.FileProvider;
@@ -409,10 +411,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
409
411
  private boolean preShowInjectedAtDocumentStart = false;
410
412
 
411
413
  public PermissionRequest currentPermissionRequest;
412
- public static final int FILE_CHOOSER_REQUEST_CODE = 1000;
413
414
  public ValueCallback<Uri> mUploadMessage;
414
415
  public ValueCallback<Uri[]> mFilePathCallback;
415
416
  FileChooserRequestSupport.FileChooserRequest activeFileChooserRequest;
417
+ private ActivityResultLauncher<Intent> cameraCaptureLauncher;
418
+ private ActivityResultLauncher<Intent> fileChooserLauncher;
416
419
  private boolean openWebViewResolved;
417
420
  private boolean isDismissing = false;
418
421
  private PermissionRequest pendingCameraLaunchPermissionRequest;
@@ -447,6 +450,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
447
450
  this.instanceId = id != null ? id : "";
448
451
  }
449
452
 
453
+ void bindToHostActivity() {
454
+ ensureFileChooserLaunchers();
455
+ registerDialogBackHandler();
456
+ }
457
+
450
458
  public String getInstanceId() {
451
459
  return instanceId;
452
460
  }
@@ -2116,10 +2124,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2116
2124
  public void presentWebView() {
2117
2125
  requestWindowFeature(Window.FEATURE_NO_TITLE);
2118
2126
  setCancelable(true);
2119
- Objects.requireNonNull(getWindow()).setFlags(
2120
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
2121
- WindowManager.LayoutParams.FLAG_FULLSCREEN
2122
- );
2123
2127
  setContentView(R.layout.activity_browser);
2124
2128
 
2125
2129
  // If custom dimensions are set, configure for touch passthrough
@@ -2143,18 +2147,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2143
2147
  if (appBar != null) appBar.setFitsSystemWindows(true);
2144
2148
  }
2145
2149
 
2146
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
2147
-
2148
- // Make status bar transparent
2149
- if (getWindow() != null) {
2150
- getWindow().setStatusBarColor(Color.TRANSPARENT);
2151
-
2152
- // Add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window
2153
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
2150
+ // Set dimensions if specified, otherwise fullscreen
2151
+ applyDimensions();
2154
2152
 
2155
- // On Android 30+ clear FLAG_TRANSLUCENT_STATUS flag
2156
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2157
- }
2153
+ SystemUiChromeSupport.prepareInitialDialogWindow(getWindow());
2158
2154
 
2159
2155
  WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(
2160
2156
  getWindow(),
@@ -2206,9 +2202,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2206
2202
  });
2207
2203
  }
2208
2204
 
2209
- // Set dimensions if specified, otherwise fullscreen
2210
- applyDimensions();
2211
-
2212
2205
  this._webView = findViewById(R.id.browser_view);
2213
2206
 
2214
2207
  this.swipeRefreshLayout = findViewById(R.id.content_browser_layout);
@@ -2583,38 +2576,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2583
2576
  }
2584
2577
 
2585
2578
  try {
2586
- if (activity instanceof androidx.activity.ComponentActivity) {
2587
- androidx.activity.ComponentActivity componentActivity =
2588
- (androidx.activity.ComponentActivity) activity;
2589
- componentActivity
2590
- .getActivityResultRegistry()
2591
- .register(
2592
- "camera_capture",
2593
- new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
2594
- (result) -> {
2595
- if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2596
- return;
2597
- }
2598
- Uri[] results = null;
2599
- if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
2600
- results = new Uri[] { request.tempCameraUri };
2601
- }
2602
- if (
2603
- FileChooserRequestSupport.completeIfActive(
2604
- request,
2605
- activeFileChooserRequest,
2606
- results
2607
- )
2608
- ) {
2609
- request.tempCameraUri = null;
2610
- clearActiveFileChooserRequest(request);
2611
- }
2612
- }
2613
- )
2614
- .launch(takePictureIntent);
2579
+ ensureFileChooserLaunchers();
2580
+ if (cameraCaptureLauncher != null) {
2581
+ cameraCaptureLauncher.launch(takePictureIntent);
2615
2582
  } else {
2616
- // Fallback for non-ComponentActivity
2617
- activity.startActivityForResult(takePictureIntent, FILE_CHOOSER_REQUEST_CODE);
2583
+ Log.e("InAppBrowser", "ComponentActivity required for camera capture, falling back to file picker");
2584
+ fallbackToFilePicker(request);
2618
2585
  }
2619
2586
  } catch (SecurityException e) {
2620
2587
  Log.e("InAppBrowser", "Security exception launching camera: " + e.getMessage(), e);
@@ -2887,6 +2854,52 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2887
2854
  registerConfigurationCallbacks();
2888
2855
  }
2889
2856
 
2857
+ private void registerDialogBackHandler() {
2858
+ setOnKeyListener((dialogInterface, keyCode, event) -> {
2859
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
2860
+ return false;
2861
+ }
2862
+ if (event.getAction() != KeyEvent.ACTION_UP) {
2863
+ return true;
2864
+ }
2865
+ if (!shouldConsumeBackPress()) {
2866
+ return false;
2867
+ }
2868
+ handleBrowserBackNavigation();
2869
+ return true;
2870
+ });
2871
+ }
2872
+
2873
+ private boolean shouldConsumeBackPress() {
2874
+ return isShowing() && !isHiddenModeActive && !backLayerActive;
2875
+ }
2876
+
2877
+ private void handleBrowserBackNavigation() {
2878
+ if (_options == null) {
2879
+ dismiss();
2880
+ return;
2881
+ }
2882
+
2883
+ if (
2884
+ _webView != null &&
2885
+ _webView.canGoBack() &&
2886
+ (TextUtils.equals(_options.getToolbarType(), "navigation") || _options.getActiveNativeNavigationForWebview())
2887
+ ) {
2888
+ _webView.goBack();
2889
+ return;
2890
+ }
2891
+
2892
+ if (_options.getDisableGoBackOnNativeApplication()) {
2893
+ return;
2894
+ }
2895
+
2896
+ String currentUrl = getUrl();
2897
+ if (_options.getCallbacks() != null) {
2898
+ _options.getCallbacks().closeEvent(currentUrl);
2899
+ }
2900
+ dismiss();
2901
+ }
2902
+
2890
2903
  private void applyHiddenMode() {
2891
2904
  Window window = getWindow();
2892
2905
  if (window == null) {
@@ -3033,7 +3046,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3033
3046
  }
3034
3047
 
3035
3048
  // Check if we need Android 15+ specific fixes
3036
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3049
+ boolean isAndroid15Plus = SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT);
3037
3050
 
3038
3051
  View toolbarView = findViewById(R.id.tool_bar);
3039
3052
 
@@ -3062,69 +3075,54 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3062
3075
 
3063
3076
  // Handle window decoration - version-specific handling
3064
3077
  if (getWindow() != null) {
3065
- if (isAndroid15Plus) {
3066
- // Android 15+: Use edge-to-edge with proper insets handling
3067
- getWindow().setDecorFitsSystemWindows(false);
3068
- getWindow().setStatusBarColor(Color.TRANSPARENT);
3069
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
3070
-
3071
- WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
3078
+ View decorView = getWindow().getDecorView();
3079
+ Integer statusBarColor = null;
3080
+ Integer navigationBarColor = Color.TRANSPARENT;
3081
+ Boolean lightStatusBars = null;
3072
3082
 
3073
- // Set status bar text color
3083
+ if (isAndroid15Plus) {
3074
3084
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3075
3085
  try {
3076
3086
  int backgroundColor = Color.parseColor(_options.getToolbarColor());
3077
- boolean isDarkBackground = isDarkColor(backgroundColor);
3078
- controller.setAppearanceLightStatusBars(!isDarkBackground);
3087
+ lightStatusBars = !isDarkColor(backgroundColor);
3079
3088
  } catch (IllegalArgumentException e) {
3080
3089
  // Ignore color parsing errors
3081
3090
  }
3082
3091
  }
3083
- } else if (Build.VERSION.SDK_INT >= 30) {
3084
- // Android 11-14: Keep navigation bar transparent but respect status bar
3085
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
3086
-
3087
- WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(getWindow(), getWindow().getDecorView());
3088
-
3089
- // Set status bar color to match toolbar or use system default
3092
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
3090
3093
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3091
3094
  try {
3092
3095
  int toolbarColor = Color.parseColor(_options.getToolbarColor());
3093
- getWindow().setStatusBarColor(toolbarColor);
3094
- boolean isDarkBackground = isDarkColor(toolbarColor);
3095
- controller.setAppearanceLightStatusBars(!isDarkBackground);
3096
+ statusBarColor = toolbarColor;
3097
+ lightStatusBars = !isDarkColor(toolbarColor);
3096
3098
  } catch (IllegalArgumentException e) {
3097
- // Follow system theme if color parsing fails
3098
3099
  boolean isDarkTheme = isDarkThemeEnabled();
3099
- int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3100
- getWindow().setStatusBarColor(statusBarColor);
3101
- controller.setAppearanceLightStatusBars(!isDarkTheme);
3100
+ statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3101
+ lightStatusBars = !isDarkTheme;
3102
3102
  }
3103
3103
  } else {
3104
- // Follow system theme if no toolbar color provided
3105
3104
  boolean isDarkTheme = isDarkThemeEnabled();
3106
- int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3107
- getWindow().setStatusBarColor(statusBarColor);
3108
- controller.setAppearanceLightStatusBars(!isDarkTheme);
3105
+ statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3106
+ lightStatusBars = !isDarkTheme;
3109
3107
  }
3110
3108
  } else {
3111
- // Pre-Android 11: Use deprecated flags for edge-to-edge navigation bar only
3112
- getWindow()
3113
- .getDecorView()
3114
- .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
3115
-
3116
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
3117
-
3118
- // Set status bar color to match toolbar
3119
3109
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3120
3110
  try {
3121
- int toolbarColor = Color.parseColor(_options.getToolbarColor());
3122
- getWindow().setStatusBarColor(toolbarColor);
3111
+ statusBarColor = Color.parseColor(_options.getToolbarColor());
3123
3112
  } catch (IllegalArgumentException e) {
3124
3113
  // Use system default
3125
3114
  }
3126
3115
  }
3127
3116
  }
3117
+
3118
+ SystemUiChromeSupport.applyDialogWindowChrome(
3119
+ getWindow(),
3120
+ decorView,
3121
+ isAndroid15Plus,
3122
+ statusBarColor,
3123
+ navigationBarColor,
3124
+ lightStatusBars
3125
+ );
3128
3126
  }
3129
3127
 
3130
3128
  requestSafeAreaInsets();
@@ -3443,7 +3441,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3443
3441
  return;
3444
3442
  }
3445
3443
 
3446
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3444
+ boolean isAndroid15Plus = SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT);
3447
3445
  View toolbarView = findBrowserContentDescendant(R.id.tool_bar);
3448
3446
  applyWindowInsetsToWebView(windowInsets, isAndroid15Plus, toolbarView);
3449
3447
  }
@@ -3515,7 +3513,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3515
3513
  return;
3516
3514
  }
3517
3515
 
3518
- boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(_options.getEnabledSafeMargin(), isEdgeToEdge);
3516
+ boolean layoutBehindNavigationBar =
3517
+ !backLayerActive && SystemUiChromeSupport.usesLayoutBehindNavigationBar(Build.VERSION.SDK_INT, isEdgeToEdge);
3518
+ boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(
3519
+ _options.getEnabledSafeMargin(),
3520
+ isEdgeToEdge,
3521
+ layoutBehindNavigationBar
3522
+ );
3519
3523
  int statusBarTop = SafeAreaInsetsSupport.resolveStatusBarTop(
3520
3524
  bars.top,
3521
3525
  bars.bottom,
@@ -4086,6 +4090,88 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4086
4090
  }
4087
4091
  }
4088
4092
 
4093
+ private void ensureFileChooserLaunchers() {
4094
+ if (!(activity instanceof ComponentActivity componentActivity)) {
4095
+ return;
4096
+ }
4097
+ if (fileChooserLauncher != null && cameraCaptureLauncher != null) {
4098
+ return;
4099
+ }
4100
+
4101
+ String launcherSuffix = (instanceId != null && !instanceId.isEmpty() ? instanceId + "_" : "") + Integer.toHexString(hashCode());
4102
+ if (cameraCaptureLauncher == null) {
4103
+ cameraCaptureLauncher = componentActivity
4104
+ .getActivityResultRegistry()
4105
+ .register(
4106
+ "inappbrowser_camera_capture_" + launcherSuffix,
4107
+ new ActivityResultContracts.StartActivityForResult(),
4108
+ this::handleCameraCaptureActivityResult
4109
+ );
4110
+ }
4111
+ if (fileChooserLauncher == null) {
4112
+ fileChooserLauncher = componentActivity
4113
+ .getActivityResultRegistry()
4114
+ .register(
4115
+ "inappbrowser_file_chooser_" + launcherSuffix,
4116
+ new ActivityResultContracts.StartActivityForResult(),
4117
+ this::handleFileChooserActivityResult
4118
+ );
4119
+ }
4120
+ }
4121
+
4122
+ private void clearActivityResultLaunchers() {
4123
+ if (cameraCaptureLauncher != null) {
4124
+ cameraCaptureLauncher.unregister();
4125
+ cameraCaptureLauncher = null;
4126
+ }
4127
+ if (fileChooserLauncher != null) {
4128
+ fileChooserLauncher.unregister();
4129
+ fileChooserLauncher = null;
4130
+ }
4131
+ }
4132
+
4133
+ private void handleCameraCaptureActivityResult(ActivityResult result) {
4134
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4135
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4136
+ return;
4137
+ }
4138
+
4139
+ Uri[] results = null;
4140
+ if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
4141
+ results = new Uri[] { request.tempCameraUri };
4142
+ }
4143
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4144
+ request.tempCameraUri = null;
4145
+ clearActiveFileChooserRequest(request);
4146
+ }
4147
+ }
4148
+
4149
+ private void handleFileChooserActivityResult(ActivityResult result) {
4150
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4151
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4152
+ return;
4153
+ }
4154
+
4155
+ Uri[] results = null;
4156
+ if (result.getResultCode() == Activity.RESULT_OK) {
4157
+ Intent data = result.getData();
4158
+ if (data != null) {
4159
+ if (data.getClipData() != null) {
4160
+ int count = data.getClipData().getItemCount();
4161
+ results = new Uri[count];
4162
+ for (int i = 0; i < count; i++) {
4163
+ results[i] = data.getClipData().getItemAt(i).getUri();
4164
+ }
4165
+ } else if (data.getData() != null) {
4166
+ results = new Uri[] { data.getData() };
4167
+ }
4168
+ }
4169
+ }
4170
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4171
+ clearActiveFileChooserRequest(request);
4172
+ }
4173
+ }
4174
+
4089
4175
  private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String[] acceptTypes, boolean isMultiple) {
4090
4176
  if (activeFileChooserRequest != null) {
4091
4177
  FileChooserRequestSupport.cancel(activeFileChooserRequest);
@@ -4106,17 +4192,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4106
4192
  tempCameraUri = activeFileChooserRequest != null ? activeFileChooserRequest.tempCameraUri : null;
4107
4193
  }
4108
4194
 
4109
- void completeLegacyFileChooserResult(Uri[] results) {
4110
- FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4111
- if (request == null) {
4112
- return;
4113
- }
4114
- if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4115
- request.tempCameraUri = null;
4116
- clearActiveFileChooserRequest(request);
4117
- }
4118
- }
4119
-
4120
4195
  private void openFileChooser(FileChooserRequestSupport.FileChooserRequest request) {
4121
4196
  if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4122
4197
  return;
@@ -4127,88 +4202,14 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4127
4202
  Log.d("InAppBrowser", "File picker using action: " + intent.getAction() + ", MIME types: " + mimeTypes);
4128
4203
 
4129
4204
  try {
4130
- if (activity instanceof androidx.activity.ComponentActivity) {
4131
- androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
4132
- componentActivity
4133
- .getActivityResultRegistry()
4134
- .register(
4135
- "file_chooser",
4136
- new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4137
- (result) -> {
4138
- if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4139
- return;
4140
- }
4141
- Uri[] results = null;
4142
- if (result.getResultCode() == Activity.RESULT_OK) {
4143
- Intent data = result.getData();
4144
- if (data != null) {
4145
- if (data.getClipData() != null) {
4146
- // Handle multiple files
4147
- int count = data.getClipData().getItemCount();
4148
- results = new Uri[count];
4149
- for (int i = 0; i < count; i++) {
4150
- results[i] = data.getClipData().getItemAt(i).getUri();
4151
- }
4152
- } else if (data.getData() != null) {
4153
- // Handle single file
4154
- results = new Uri[] { data.getData() };
4155
- }
4156
- }
4157
- }
4158
- if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4159
- clearActiveFileChooserRequest(request);
4160
- }
4161
- }
4162
- )
4163
- .launch(Intent.createChooser(intent, "Select File"));
4164
- } else {
4165
- // Fallback for non-ComponentActivity
4166
- activity.startActivityForResult(Intent.createChooser(intent, "Select File"), FILE_CHOOSER_REQUEST_CODE);
4167
- }
4205
+ launchFileChooserIntent(Intent.createChooser(intent, "Select File"), request);
4168
4206
  } catch (ActivityNotFoundException e) {
4169
4207
  // If no app can handle the specific MIME type, try with a more generic one
4170
4208
  Log.e("InAppBrowser", "No app available for types: " + mimeTypes + ", trying with */*");
4171
4209
  intent.setType("*/*");
4172
4210
  intent.removeExtra(Intent.EXTRA_MIME_TYPES);
4173
4211
  try {
4174
- if (activity instanceof androidx.activity.ComponentActivity) {
4175
- androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
4176
- componentActivity
4177
- .getActivityResultRegistry()
4178
- .register(
4179
- "file_chooser",
4180
- new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4181
- (result) -> {
4182
- if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4183
- return;
4184
- }
4185
- Uri[] results = null;
4186
- if (result.getResultCode() == Activity.RESULT_OK) {
4187
- Intent data = result.getData();
4188
- if (data != null) {
4189
- if (data.getClipData() != null) {
4190
- // Handle multiple files
4191
- int count = data.getClipData().getItemCount();
4192
- results = new Uri[count];
4193
- for (int i = 0; i < count; i++) {
4194
- results[i] = data.getClipData().getItemAt(i).getUri();
4195
- }
4196
- } else if (data.getData() != null) {
4197
- // Handle single file
4198
- results = new Uri[] { data.getData() };
4199
- }
4200
- }
4201
- }
4202
- if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4203
- clearActiveFileChooserRequest(request);
4204
- }
4205
- }
4206
- )
4207
- .launch(Intent.createChooser(intent, "Select File"));
4208
- } else {
4209
- // Fallback for non-ComponentActivity
4210
- activity.startActivityForResult(Intent.createChooser(intent, "Select File"), FILE_CHOOSER_REQUEST_CODE);
4211
- }
4212
+ launchFileChooserIntent(Intent.createChooser(intent, "Select File"), request);
4212
4213
  } catch (ActivityNotFoundException ex) {
4213
4214
  // If still failing, report error
4214
4215
  Log.e("InAppBrowser", "No app can handle file picker", ex);
@@ -4219,6 +4220,18 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4219
4220
  }
4220
4221
  }
4221
4222
 
4223
+ private void launchFileChooserIntent(Intent chooserIntent, FileChooserRequestSupport.FileChooserRequest request) {
4224
+ ensureFileChooserLaunchers();
4225
+ if (fileChooserLauncher == null) {
4226
+ Log.e("InAppBrowser", "ComponentActivity required for file chooser");
4227
+ if (FileChooserRequestSupport.cancelIfActive(request, activeFileChooserRequest)) {
4228
+ clearActiveFileChooserRequest(request);
4229
+ }
4230
+ return;
4231
+ }
4232
+ fileChooserLauncher.launch(chooserIntent);
4233
+ }
4234
+
4222
4235
  public void reload() {
4223
4236
  if (_webView == null) {
4224
4237
  Log.w("InAppBrowser", "Cannot reload - WebView is null");
@@ -4700,8 +4713,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4700
4713
 
4701
4714
  // Also ensure status bar gets the color
4702
4715
  if (getWindow() != null) {
4703
- // Set status bar color
4704
- getWindow().setStatusBarColor(toolbarColor);
4716
+ SystemUiChromeSupport.applyLegacySystemBarColors(getWindow(), toolbarColor, null);
4705
4717
 
4706
4718
  // Determine proper status bar text color (light or dark icons)
4707
4719
  boolean isDarkBackground = isDarkColor(toolbarColor);
@@ -6165,21 +6177,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6165
6177
  return false;
6166
6178
  }
6167
6179
 
6168
- @Override
6169
- public void onBackPressed() {
6170
- if (
6171
- _webView != null &&
6172
- _webView.canGoBack() &&
6173
- (TextUtils.equals(_options.getToolbarType(), "navigation") || _options.getActiveNativeNavigationForWebview())
6174
- ) {
6175
- _webView.goBack();
6176
- } else if (!_options.getDisableGoBackOnNativeApplication()) {
6177
- String currentUrl = getUrl();
6178
- _options.getCallbacks().closeEvent(currentUrl);
6179
- super.onBackPressed();
6180
- }
6181
- }
6182
-
6183
6180
  @Override
6184
6181
  public boolean onKeyDown(int keyCode, KeyEvent event) {
6185
6182
  // Forward volume key events to the MainActivity
@@ -6384,6 +6381,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6384
6381
  // Clear any remaining proxied requests
6385
6382
  proxiedRequestsHashmap.clear();
6386
6383
 
6384
+ clearActivityResultLaunchers();
6385
+
6387
6386
  try {
6388
6387
  super.dismiss();
6389
6388
  } catch (Exception e) {
@@ -415,7 +415,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
415
415
  case aware = "AWARE"
416
416
  case fakeVisible = "FAKE_VISIBLE"
417
417
  }
418
- private let pluginVersion: String = "8.15.3"
418
+ private let pluginVersion: String = "8.15.4"
419
419
  public let identifier = "CapgoInAppBrowserPlugin"
420
420
  public let jsName = "CapgoInAppBrowser"
421
421
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.15.3",
3
+ "version": "8.15.4",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",