@capgo/capacitor-inappbrowser 8.15.2 → 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.
- package/README.md +4 -2
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +15 -65
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/InAppBrowserPlugin.java +1 -2
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/OpenTimeBrowsingDataClearSupport.java +40 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +20 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/SafeAreaInsetsSupport.java +25 -5
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/SystemUiChromeSupport.java +109 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewCustomFullscreenSupport.java +49 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +344 -200
- package/dist/docs.json +34 -2
- package/dist/esm/definitions.d.ts +30 -5
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +36 -1
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +13 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
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
|
-
|
|
2617
|
-
|
|
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
|
|
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
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
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
|
-
|
|
3225
|
+
if (isAndroid15Plus) {
|
|
3074
3226
|
if (_options.getToolbarColor() != null && !_options.getToolbarColor().isEmpty()) {
|
|
3075
3227
|
try {
|
|
3076
3228
|
int backgroundColor = Color.parseColor(_options.getToolbarColor());
|
|
3077
|
-
|
|
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 >=
|
|
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
|
-
|
|
3094
|
-
|
|
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
|
-
|
|
3100
|
-
|
|
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
|
-
|
|
3107
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|