diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
index 3f04adf976..c84923ca8b 100644
--- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
+++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java
@@ -7,7 +7,6 @@
import android.os.Build;
import android.util.TypedValue;
import android.view.View;
-import android.view.ViewGroup;
import android.view.Window;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
@@ -36,6 +35,11 @@ public class SystemBars extends Plugin {
static final String INSETS_HANDLING_CSS = "css";
static final String INSETS_HANDLING_DISABLE = "disable";
+ // https://issues.chromium.org/issues/40699457
+ private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
+ // https://issues.chromium.org/issues/457682720
+ private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144;
+
static final String viewportMetaJSFunction = """
function capacitorSystemBarsCheckMetaViewport() {
const meta = document.querySelectorAll("meta[name=viewport]");
@@ -94,7 +98,7 @@ private void initSystemBars() {
}
initWindowInsetsListener();
- initSafeAreaInsets();
+ initSafeAreaCSSVariables();
getBridge().executeOnMainThread(() -> {
setStyle(style, "");
@@ -157,7 +161,7 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
}
- private void initSafeAreaInsets() {
+ private void initSafeAreaCSSVariables() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
View v = (View) this.getBridge().getWebView().getParent();
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
@@ -169,41 +173,57 @@ private void initSafeAreaInsets() {
}
private void initWindowInsetsListener() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
- ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
- boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139;
+ ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
+ boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
+
+ Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
+ Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
+ boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
- if (hasViewportCover) {
+ if (shouldPassthroughInsets) {
+ // We need to correct for a possible shown IME
+ v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
Insets safeAreaInsets = calcSafeAreaInsets(insets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
}
- if (hasBrokenWebViewVersion) {
- if (hasViewportCover && v.hasWindowFocus() && v.isShown()) {
- boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
- if (keyboardVisible) {
- Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
- setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom));
- } else {
- setViewMargins(v, Insets.NONE);
- }
-
- return WindowInsetsCompat.CONSUMED;
- }
- }
+ return new WindowInsetsCompat.Builder(insets)
+ .setInsets(
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
+ Insets.of(
+ systemBarsInsets.left,
+ systemBarsInsets.top,
+ systemBarsInsets.right,
+ getBottomInset(systemBarsInsets, keyboardVisible)
+ )
+ )
+ .build();
+ }
- return insets;
- });
- }
- }
+ // We need to correct for a possible shown IME
+ v.setPadding(
+ systemBarsInsets.left,
+ systemBarsInsets.top,
+ systemBarsInsets.right,
+ keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
+ );
+
+ // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
+ // So we have to explicitly set insets to `0`
+ // See: https://issues.chromium.org/issues/461332423
+ WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
+ .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
+ .build();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
+ Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
+ injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
+ }
- private void setViewMargins(View v, Insets insets) {
- ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
- mlp.leftMargin = insets.left;
- mlp.bottomMargin = insets.bottom;
- mlp.rightMargin = insets.right;
- mlp.topMargin = insets.top;
- v.setLayoutParams(mlp);
+ return newInsets;
+ });
}
private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
@@ -305,4 +325,18 @@ private Integer getWebViewMajorVersion() {
return 0;
}
+
+ private int getBottomInset(Insets systemBarsInsets, boolean keyboardVisible) {
+ if (getWebViewMajorVersion() < WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX) {
+ // This is a workaround for webview versions that have a bug
+ // that causes the bottom inset to be incorrect if the IME is visible
+ // See: https://issues.chromium.org/issues/457682720
+
+ if (keyboardVisible) {
+ return 0;
+ }
+ }
+
+ return systemBarsInsets.bottom;
+ }
}
diff --git a/cli/src/declarations.ts b/cli/src/declarations.ts
index 6612eec777..5eacf5e654 100644
--- a/cli/src/declarations.ts
+++ b/cli/src/declarations.ts
@@ -713,7 +713,7 @@ export interface PluginsConfig {
*
* `css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
*
- * `disable` = Disable all inset handling.
+ * `disable` = Disable CSS variables injection.
*
* @default "css"
*/
diff --git a/core/system-bars.md b/core/system-bars.md
index c7654063bc..67275077af 100644
--- a/core/system-bars.md
+++ b/core/system-bars.md
@@ -73,7 +73,7 @@ const setStatusBarAnimation = async () => {
## Configuration
| Prop | Type | Description | Default |
| ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ |
-| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable all inset handling. | css |
+| **`insetsHandling`** | string | Specifies how to handle problematic insets on Android. This option is only supported on Android.
`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
`disable` = Disable CSS variables injection. | css |
| **`style`** | string | The style of the text and icons of the system bars. | DEFAULT |
| **`hidden`** | boolean | Hide the system bars on start. | false |
| **`animation`** | string | The type of status bar animation used when showing or hiding. This option is only supported on iOS. | FADE |