@capgo/capacitor-inappbrowser 8.15.3 → 8.16.2

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.
@@ -69,7 +69,10 @@ 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;
73
+ import androidx.activity.OnBackPressedCallback;
72
74
  import androidx.activity.result.ActivityResult;
75
+ import androidx.activity.result.ActivityResultLauncher;
73
76
  import androidx.activity.result.contract.ActivityResultContracts;
74
77
  import androidx.coordinatorlayout.widget.CoordinatorLayout;
75
78
  import androidx.core.content.FileProvider;
@@ -335,6 +338,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
335
338
  private SwipeRefreshLayout swipeRefreshLayout;
336
339
  private boolean reloadFromGestureInProgress = false;
337
340
  private WebView _webView;
341
+ // HTML5/iframe fullscreen (e.g. embedded YouTube) routes through WebChromeClient custom views.
342
+ private View customFullscreenView;
343
+ private WebChromeClient.CustomViewCallback customViewCallback;
344
+ private FrameLayout fullscreenContainer;
345
+ private Window customFullscreenWindow;
346
+ private OnBackPressedCallback customFullscreenBackCallback;
338
347
  private Toolbar _toolbar;
339
348
  private Options _options = null;
340
349
  private final Context _context;
@@ -409,10 +418,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
409
418
  private boolean preShowInjectedAtDocumentStart = false;
410
419
 
411
420
  public PermissionRequest currentPermissionRequest;
412
- public static final int FILE_CHOOSER_REQUEST_CODE = 1000;
413
421
  public ValueCallback<Uri> mUploadMessage;
414
422
  public ValueCallback<Uri[]> mFilePathCallback;
415
423
  FileChooserRequestSupport.FileChooserRequest activeFileChooserRequest;
424
+ private ActivityResultLauncher<Intent> cameraCaptureLauncher;
425
+ private ActivityResultLauncher<Intent> fileChooserLauncher;
416
426
  private boolean openWebViewResolved;
417
427
  private boolean isDismissing = false;
418
428
  private PermissionRequest pendingCameraLaunchPermissionRequest;
@@ -447,6 +457,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
447
457
  this.instanceId = id != null ? id : "";
448
458
  }
449
459
 
460
+ void bindToHostActivity() {
461
+ ensureFileChooserLaunchers();
462
+ registerDialogBackHandler();
463
+ }
464
+
450
465
  public String getInstanceId() {
451
466
  return instanceId;
452
467
  }
@@ -2116,10 +2131,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2116
2131
  public void presentWebView() {
2117
2132
  requestWindowFeature(Window.FEATURE_NO_TITLE);
2118
2133
  setCancelable(true);
2119
- Objects.requireNonNull(getWindow()).setFlags(
2120
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
2121
- WindowManager.LayoutParams.FLAG_FULLSCREEN
2122
- );
2123
2134
  setContentView(R.layout.activity_browser);
2124
2135
 
2125
2136
  // If custom dimensions are set, configure for touch passthrough
@@ -2143,18 +2154,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2143
2154
  if (appBar != null) appBar.setFitsSystemWindows(true);
2144
2155
  }
2145
2156
 
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);
2157
+ // Set dimensions if specified, otherwise fullscreen
2158
+ applyDimensions();
2154
2159
 
2155
- // On Android 30+ clear FLAG_TRANSLUCENT_STATUS flag
2156
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2157
- }
2160
+ SystemUiChromeSupport.prepareInitialDialogWindow(getWindow());
2158
2161
 
2159
2162
  WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(
2160
2163
  getWindow(),
@@ -2206,9 +2209,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2206
2209
  });
2207
2210
  }
2208
2211
 
2209
- // Set dimensions if specified, otherwise fullscreen
2210
- applyDimensions();
2211
-
2212
2212
  this._webView = findViewById(R.id.browser_view);
2213
2213
 
2214
2214
  this.swipeRefreshLayout = findViewById(R.id.content_browser_layout);
@@ -2250,13 +2250,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2250
2250
  if (!_options.getPersistWebViewData()) {
2251
2251
  _webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
2252
2252
  _webView.getSettings().setDatabaseEnabled(false);
2253
- _webView.getSettings().setDomStorageEnabled(false);
2254
2253
  _webView.clearCache(true);
2255
2254
  _webView.clearHistory();
2256
2255
  _webView.clearFormData();
2257
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
2258
- CookieManager.getInstance().setAcceptThirdPartyCookies(_webView, false);
2259
- }
2260
2256
  }
2261
2257
  _webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
2262
2258
  injectDocumentStartJavaScriptInterface();
@@ -2316,6 +2312,17 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2316
2312
  return super.onConsoleMessage(consoleMessage);
2317
2313
  }
2318
2314
 
2315
+ // Enable HTML5/iframe fullscreen (e.g. embedded YouTube player fullscreen button).
2316
+ @Override
2317
+ public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
2318
+ showCustomFullscreenView(view, callback);
2319
+ }
2320
+
2321
+ @Override
2322
+ public void onHideCustomView() {
2323
+ exitCustomFullscreenView();
2324
+ }
2325
+
2319
2326
  // Enable file open dialog
2320
2327
  @Override
2321
2328
  public boolean onShowFileChooser(
@@ -2583,38 +2590,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2583
2590
  }
2584
2591
 
2585
2592
  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);
2593
+ ensureFileChooserLaunchers();
2594
+ if (cameraCaptureLauncher != null) {
2595
+ cameraCaptureLauncher.launch(takePictureIntent);
2615
2596
  } else {
2616
- // Fallback for non-ComponentActivity
2617
- activity.startActivityForResult(takePictureIntent, FILE_CHOOSER_REQUEST_CODE);
2597
+ Log.e("InAppBrowser", "ComponentActivity required for camera capture, falling back to file picker");
2598
+ fallbackToFilePicker(request);
2618
2599
  }
2619
2600
  } catch (SecurityException e) {
2620
2601
  Log.e("InAppBrowser", "Security exception launching camera: " + e.getMessage(), e);
@@ -2817,6 +2798,15 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2817
2798
  Map<String, String> requestHeaders = buildRequestHeadersExcludingUserAgent();
2818
2799
  applyWebViewUserAgent();
2819
2800
 
2801
+ if (_options.getClearCookiesOnOpen() || _options.getClearCacheOnOpen()) {
2802
+ OpenTimeBrowsingDataClearSupport.applyBeforeFirstNavigation(
2803
+ CookieManager.getInstance(),
2804
+ _webView,
2805
+ _options.getClearCookiesOnOpen(),
2806
+ _options.getClearCacheOnOpen()
2807
+ );
2808
+ }
2809
+
2820
2810
  // Load URL with optional HTTP method and body
2821
2811
  String httpMethod = _options.getHttpMethod();
2822
2812
  String httpBody = _options.getHttpBody();
@@ -2887,6 +2877,171 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2887
2877
  registerConfigurationCallbacks();
2888
2878
  }
2889
2879
 
2880
+ private void registerDialogBackHandler() {
2881
+ setOnKeyListener((dialogInterface, keyCode, event) -> {
2882
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
2883
+ return false;
2884
+ }
2885
+ if (event.getAction() != KeyEvent.ACTION_UP) {
2886
+ return true;
2887
+ }
2888
+ if (!shouldConsumeBackPress()) {
2889
+ return false;
2890
+ }
2891
+ handleBrowserBackNavigation();
2892
+ return true;
2893
+ });
2894
+ }
2895
+
2896
+ private boolean shouldConsumeBackPress() {
2897
+ return isShowing() && !isHiddenModeActive && !backLayerActive;
2898
+ }
2899
+
2900
+ private void handleBrowserBackNavigation() {
2901
+ if (WebViewCustomFullscreenSupport.shouldConsumeBackPress(customFullscreenView != null)) {
2902
+ exitCustomFullscreenView();
2903
+ return;
2904
+ }
2905
+
2906
+ if (_options == null) {
2907
+ dismiss();
2908
+ return;
2909
+ }
2910
+
2911
+ if (
2912
+ _webView != null &&
2913
+ _webView.canGoBack() &&
2914
+ (TextUtils.equals(_options.getToolbarType(), "navigation") || _options.getActiveNativeNavigationForWebview())
2915
+ ) {
2916
+ _webView.goBack();
2917
+ return;
2918
+ }
2919
+
2920
+ if (_options.getDisableGoBackOnNativeApplication()) {
2921
+ return;
2922
+ }
2923
+
2924
+ String currentUrl = getUrl();
2925
+ if (_options.getCallbacks() != null) {
2926
+ _options.getCallbacks().closeEvent(currentUrl);
2927
+ }
2928
+ dismiss();
2929
+ }
2930
+
2931
+ // Shows a native fullscreen view requested via the HTML5 Fullscreen API (e.g. embedded YouTube).
2932
+ private void showCustomFullscreenView(View view, WebChromeClient.CustomViewCallback callback) {
2933
+ if (WebViewCustomFullscreenSupport.shouldRejectDuplicateShow(customFullscreenView != null)) {
2934
+ callback.onCustomViewHidden();
2935
+ return;
2936
+ }
2937
+
2938
+ Window window = resolveCustomFullscreenWindow();
2939
+ View decorView = window != null ? window.getDecorView() : null;
2940
+ if (!(decorView instanceof ViewGroup)) {
2941
+ callback.onCustomViewHidden();
2942
+ return;
2943
+ }
2944
+
2945
+ customFullscreenView = view;
2946
+ customViewCallback = callback;
2947
+ customFullscreenWindow = window;
2948
+
2949
+ if (fullscreenContainer == null) {
2950
+ fullscreenContainer = new FrameLayout(getContext());
2951
+ fullscreenContainer.setBackgroundColor(Color.BLACK);
2952
+ } else if (fullscreenContainer.getParent() instanceof ViewGroup oldParent) {
2953
+ oldParent.removeView(fullscreenContainer);
2954
+ }
2955
+ ((ViewGroup) decorView).addView(
2956
+ fullscreenContainer,
2957
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
2958
+ );
2959
+ fullscreenContainer.addView(
2960
+ view,
2961
+ new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
2962
+ );
2963
+ fullscreenContainer.setVisibility(View.VISIBLE);
2964
+
2965
+ if (_webView != null) {
2966
+ _webView.setVisibility(View.INVISIBLE);
2967
+ }
2968
+
2969
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
2970
+ decorView.setSystemUiVisibility(WebViewCustomFullscreenSupport.immersiveFullscreenSystemUiVisibility());
2971
+
2972
+ if (WebViewCustomFullscreenSupport.shouldRegisterHostBackHandler(backLayerActive)) {
2973
+ registerCustomFullscreenBackHandler();
2974
+ }
2975
+ }
2976
+
2977
+ private Window resolveCustomFullscreenWindow() {
2978
+ if (WebViewCustomFullscreenSupport.shouldUseHostActivityWindow(backLayerActive) && activity != null) {
2979
+ Window hostWindow = activity.getWindow();
2980
+ if (hostWindow != null) {
2981
+ return hostWindow;
2982
+ }
2983
+ }
2984
+ return getWindow();
2985
+ }
2986
+
2987
+ private void registerCustomFullscreenBackHandler() {
2988
+ if (!(activity instanceof ComponentActivity componentActivity) || customFullscreenBackCallback != null) {
2989
+ return;
2990
+ }
2991
+ customFullscreenBackCallback = new OnBackPressedCallback(true) {
2992
+ @Override
2993
+ public void handleOnBackPressed() {
2994
+ if (WebViewCustomFullscreenSupport.isCustomFullscreenActive(customFullscreenView)) {
2995
+ exitCustomFullscreenView();
2996
+ }
2997
+ }
2998
+ };
2999
+ componentActivity.getOnBackPressedDispatcher().addCallback(componentActivity, customFullscreenBackCallback);
3000
+ }
3001
+
3002
+ private void unregisterCustomFullscreenBackHandler() {
3003
+ if (customFullscreenBackCallback != null) {
3004
+ customFullscreenBackCallback.remove();
3005
+ customFullscreenBackCallback = null;
3006
+ }
3007
+ }
3008
+
3009
+ private void exitCustomFullscreenView() {
3010
+ if (!WebViewCustomFullscreenSupport.isCustomFullscreenActive(customFullscreenView)) {
3011
+ return;
3012
+ }
3013
+
3014
+ if (fullscreenContainer != null) {
3015
+ fullscreenContainer.removeView(customFullscreenView);
3016
+ fullscreenContainer.setVisibility(View.GONE);
3017
+ if (fullscreenContainer.getParent() instanceof ViewGroup parent) {
3018
+ parent.removeView(fullscreenContainer);
3019
+ }
3020
+ }
3021
+ if (_webView != null) {
3022
+ _webView.setVisibility(View.VISIBLE);
3023
+ }
3024
+
3025
+ Window window = customFullscreenWindow != null ? customFullscreenWindow : getWindow();
3026
+ if (window != null) {
3027
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
3028
+ window.getDecorView().setSystemUiVisibility(WebViewCustomFullscreenSupport.restoredSystemUiVisibility());
3029
+ reapplyInsetsFromWindowRoot();
3030
+ if (SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT)) {
3031
+ refreshEdgeToEdgeChrome();
3032
+ }
3033
+ }
3034
+
3035
+ unregisterCustomFullscreenBackHandler();
3036
+
3037
+ if (customViewCallback != null) {
3038
+ customViewCallback.onCustomViewHidden();
3039
+ }
3040
+ customFullscreenView = null;
3041
+ customViewCallback = null;
3042
+ customFullscreenWindow = null;
3043
+ }
3044
+
2890
3045
  private void applyHiddenMode() {
2891
3046
  Window window = getWindow();
2892
3047
  if (window == null) {
@@ -3033,7 +3188,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3033
3188
  }
3034
3189
 
3035
3190
  // Check if we need Android 15+ specific fixes
3036
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3191
+ boolean isAndroid15Plus = SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT);
3037
3192
 
3038
3193
  View toolbarView = findViewById(R.id.tool_bar);
3039
3194
 
@@ -3062,69 +3217,54 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3062
3217
 
3063
3218
  // Handle window decoration - version-specific handling
3064
3219
  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());
3220
+ View decorView = getWindow().getDecorView();
3221
+ Integer statusBarColor = null;
3222
+ Integer navigationBarColor = Color.TRANSPARENT;
3223
+ Boolean lightStatusBars = null;
3072
3224
 
3073
- // Set status bar text color
3225
+ if (isAndroid15Plus) {
3074
3226
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3075
3227
  try {
3076
3228
  int backgroundColor = Color.parseColor(_options.getToolbarColor());
3077
- boolean isDarkBackground = isDarkColor(backgroundColor);
3078
- controller.setAppearanceLightStatusBars(!isDarkBackground);
3229
+ lightStatusBars = !isDarkColor(backgroundColor);
3079
3230
  } catch (IllegalArgumentException e) {
3080
3231
  // Ignore color parsing errors
3081
3232
  }
3082
3233
  }
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
3234
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
3090
3235
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3091
3236
  try {
3092
3237
  int toolbarColor = Color.parseColor(_options.getToolbarColor());
3093
- getWindow().setStatusBarColor(toolbarColor);
3094
- boolean isDarkBackground = isDarkColor(toolbarColor);
3095
- controller.setAppearanceLightStatusBars(!isDarkBackground);
3238
+ statusBarColor = toolbarColor;
3239
+ lightStatusBars = !isDarkColor(toolbarColor);
3096
3240
  } catch (IllegalArgumentException e) {
3097
- // Follow system theme if color parsing fails
3098
3241
  boolean isDarkTheme = isDarkThemeEnabled();
3099
- int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3100
- getWindow().setStatusBarColor(statusBarColor);
3101
- controller.setAppearanceLightStatusBars(!isDarkTheme);
3242
+ statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3243
+ lightStatusBars = !isDarkTheme;
3102
3244
  }
3103
3245
  } else {
3104
- // Follow system theme if no toolbar color provided
3105
3246
  boolean isDarkTheme = isDarkThemeEnabled();
3106
- int statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3107
- getWindow().setStatusBarColor(statusBarColor);
3108
- controller.setAppearanceLightStatusBars(!isDarkTheme);
3247
+ statusBarColor = isDarkTheme ? Color.BLACK : Color.WHITE;
3248
+ lightStatusBars = !isDarkTheme;
3109
3249
  }
3110
3250
  } 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
3251
  if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
3120
3252
  try {
3121
- int toolbarColor = Color.parseColor(_options.getToolbarColor());
3122
- getWindow().setStatusBarColor(toolbarColor);
3253
+ statusBarColor = Color.parseColor(_options.getToolbarColor());
3123
3254
  } catch (IllegalArgumentException e) {
3124
3255
  // Use system default
3125
3256
  }
3126
3257
  }
3127
3258
  }
3259
+
3260
+ SystemUiChromeSupport.applyDialogWindowChrome(
3261
+ getWindow(),
3262
+ decorView,
3263
+ isAndroid15Plus,
3264
+ statusBarColor,
3265
+ navigationBarColor,
3266
+ lightStatusBars
3267
+ );
3128
3268
  }
3129
3269
 
3130
3270
  requestSafeAreaInsets();
@@ -3443,7 +3583,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3443
3583
  return;
3444
3584
  }
3445
3585
 
3446
- boolean isAndroid15Plus = Build.VERSION.SDK_INT >= 35;
3586
+ boolean isAndroid15Plus = SystemUiChromeSupport.requiresEdgeToEdgeChrome(Build.VERSION.SDK_INT);
3447
3587
  View toolbarView = findBrowserContentDescendant(R.id.tool_bar);
3448
3588
  applyWindowInsetsToWebView(windowInsets, isAndroid15Plus, toolbarView);
3449
3589
  }
@@ -3515,7 +3655,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3515
3655
  return;
3516
3656
  }
3517
3657
 
3518
- boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(_options.getEnabledSafeMargin(), isEdgeToEdge);
3658
+ boolean layoutBehindNavigationBar =
3659
+ !backLayerActive && SystemUiChromeSupport.usesLayoutBehindNavigationBar(Build.VERSION.SDK_INT, isEdgeToEdge);
3660
+ boolean applyBottomInset = SafeAreaInsetsSupport.shouldInsetBottomForContainer(
3661
+ _options.getEnabledSafeMargin(),
3662
+ isEdgeToEdge,
3663
+ layoutBehindNavigationBar
3664
+ );
3519
3665
  int statusBarTop = SafeAreaInsetsSupport.resolveStatusBarTop(
3520
3666
  bars.top,
3521
3667
  bars.bottom,
@@ -3570,7 +3716,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
3570
3716
  container.setPadding(bars.left, padTop, bars.right, padBottom);
3571
3717
  }
3572
3718
 
3573
- injectSafeAreaCssVariables(padTop, padBottom, bars.left, bars.right);
3719
+ int cssTop = SafeAreaInsetsSupport.cssTopInset(_options.getEnabledSafeTopMargin(), padTop);
3720
+ int cssBottom = SafeAreaInsetsSupport.cssBottomInset(_options.getEnabledSafeMargin(), safeBottomInset);
3721
+ injectSafeAreaCssVariables(cssTop, cssBottom, bars.left, bars.right);
3574
3722
  }
3575
3723
 
3576
3724
  private void configureBlankToolbarLayout() {
@@ -4086,6 +4234,88 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4086
4234
  }
4087
4235
  }
4088
4236
 
4237
+ private void ensureFileChooserLaunchers() {
4238
+ if (!(activity instanceof ComponentActivity componentActivity)) {
4239
+ return;
4240
+ }
4241
+ if (fileChooserLauncher != null && cameraCaptureLauncher != null) {
4242
+ return;
4243
+ }
4244
+
4245
+ String launcherSuffix = (instanceId != null && !instanceId.isEmpty() ? instanceId + "_" : "") + Integer.toHexString(hashCode());
4246
+ if (cameraCaptureLauncher == null) {
4247
+ cameraCaptureLauncher = componentActivity
4248
+ .getActivityResultRegistry()
4249
+ .register(
4250
+ "inappbrowser_camera_capture_" + launcherSuffix,
4251
+ new ActivityResultContracts.StartActivityForResult(),
4252
+ this::handleCameraCaptureActivityResult
4253
+ );
4254
+ }
4255
+ if (fileChooserLauncher == null) {
4256
+ fileChooserLauncher = componentActivity
4257
+ .getActivityResultRegistry()
4258
+ .register(
4259
+ "inappbrowser_file_chooser_" + launcherSuffix,
4260
+ new ActivityResultContracts.StartActivityForResult(),
4261
+ this::handleFileChooserActivityResult
4262
+ );
4263
+ }
4264
+ }
4265
+
4266
+ private void clearActivityResultLaunchers() {
4267
+ if (cameraCaptureLauncher != null) {
4268
+ cameraCaptureLauncher.unregister();
4269
+ cameraCaptureLauncher = null;
4270
+ }
4271
+ if (fileChooserLauncher != null) {
4272
+ fileChooserLauncher.unregister();
4273
+ fileChooserLauncher = null;
4274
+ }
4275
+ }
4276
+
4277
+ private void handleCameraCaptureActivityResult(ActivityResult result) {
4278
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4279
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4280
+ return;
4281
+ }
4282
+
4283
+ Uri[] results = null;
4284
+ if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
4285
+ results = new Uri[] { request.tempCameraUri };
4286
+ }
4287
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4288
+ request.tempCameraUri = null;
4289
+ clearActiveFileChooserRequest(request);
4290
+ }
4291
+ }
4292
+
4293
+ private void handleFileChooserActivityResult(ActivityResult result) {
4294
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4295
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4296
+ return;
4297
+ }
4298
+
4299
+ Uri[] results = null;
4300
+ if (result.getResultCode() == Activity.RESULT_OK) {
4301
+ Intent data = result.getData();
4302
+ if (data != null) {
4303
+ if (data.getClipData() != null) {
4304
+ int count = data.getClipData().getItemCount();
4305
+ results = new Uri[count];
4306
+ for (int i = 0; i < count; i++) {
4307
+ results[i] = data.getClipData().getItemAt(i).getUri();
4308
+ }
4309
+ } else if (data.getData() != null) {
4310
+ results = new Uri[] { data.getData() };
4311
+ }
4312
+ }
4313
+ }
4314
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4315
+ clearActiveFileChooserRequest(request);
4316
+ }
4317
+ }
4318
+
4089
4319
  private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String[] acceptTypes, boolean isMultiple) {
4090
4320
  if (activeFileChooserRequest != null) {
4091
4321
  FileChooserRequestSupport.cancel(activeFileChooserRequest);
@@ -4106,17 +4336,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4106
4336
  tempCameraUri = activeFileChooserRequest != null ? activeFileChooserRequest.tempCameraUri : null;
4107
4337
  }
4108
4338
 
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
4339
  private void openFileChooser(FileChooserRequestSupport.FileChooserRequest request) {
4121
4340
  if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4122
4341
  return;
@@ -4127,88 +4346,14 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4127
4346
  Log.d("InAppBrowser", "File picker using action: " + intent.getAction() + ", MIME types: " + mimeTypes);
4128
4347
 
4129
4348
  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
- }
4349
+ launchFileChooserIntent(Intent.createChooser(intent, "Select File"), request);
4168
4350
  } catch (ActivityNotFoundException e) {
4169
4351
  // If no app can handle the specific MIME type, try with a more generic one
4170
4352
  Log.e("InAppBrowser", "No app available for types: " + mimeTypes + ", trying with */*");
4171
4353
  intent.setType("*/*");
4172
4354
  intent.removeExtra(Intent.EXTRA_MIME_TYPES);
4173
4355
  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
- }
4356
+ launchFileChooserIntent(Intent.createChooser(intent, "Select File"), request);
4212
4357
  } catch (ActivityNotFoundException ex) {
4213
4358
  // If still failing, report error
4214
4359
  Log.e("InAppBrowser", "No app can handle file picker", ex);
@@ -4219,6 +4364,18 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4219
4364
  }
4220
4365
  }
4221
4366
 
4367
+ private void launchFileChooserIntent(Intent chooserIntent, FileChooserRequestSupport.FileChooserRequest request) {
4368
+ ensureFileChooserLaunchers();
4369
+ if (fileChooserLauncher == null) {
4370
+ Log.e("InAppBrowser", "ComponentActivity required for file chooser");
4371
+ if (FileChooserRequestSupport.cancelIfActive(request, activeFileChooserRequest)) {
4372
+ clearActiveFileChooserRequest(request);
4373
+ }
4374
+ return;
4375
+ }
4376
+ fileChooserLauncher.launch(chooserIntent);
4377
+ }
4378
+
4222
4379
  public void reload() {
4223
4380
  if (_webView == null) {
4224
4381
  Log.w("InAppBrowser", "Cannot reload - WebView is null");
@@ -4700,8 +4857,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4700
4857
 
4701
4858
  // Also ensure status bar gets the color
4702
4859
  if (getWindow() != null) {
4703
- // Set status bar color
4704
- getWindow().setStatusBarColor(toolbarColor);
4860
+ SystemUiChromeSupport.applyLegacySystemBarColors(getWindow(), toolbarColor, null);
4705
4861
 
4706
4862
  // Determine proper status bar text color (light or dark icons)
4707
4863
  boolean isDarkBackground = isDarkColor(toolbarColor);
@@ -6165,21 +6321,6 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6165
6321
  return false;
6166
6322
  }
6167
6323
 
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
6324
  @Override
6184
6325
  public boolean onKeyDown(int keyCode, KeyEvent event) {
6185
6326
  // Forward volume key events to the MainActivity
@@ -6256,6 +6397,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6256
6397
 
6257
6398
  @Override
6258
6399
  public void dismiss() {
6400
+ exitCustomFullscreenView();
6259
6401
  unregisterConfigurationCallbacks();
6260
6402
  scheduleHostWebViewInsetRestore();
6261
6403
  detachBackLayer();
@@ -6384,6 +6526,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6384
6526
  // Clear any remaining proxied requests
6385
6527
  proxiedRequestsHashmap.clear();
6386
6528
 
6529
+ clearActivityResultLaunchers();
6530
+
6387
6531
  try {
6388
6532
  super.dismiss();
6389
6533
  } catch (Exception e) {