Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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]");
Expand Down Expand Up @@ -94,7 +98,7 @@ private void initSystemBars() {
}

initWindowInsetsListener();
initSafeAreaInsets();
initSafeAreaCSSVariables();

getBridge().executeOnMainThread(() -> {
setStyle(style, "");
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion cli/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
*/
Expand Down
2 changes: 1 addition & 1 deletion core/system-bars.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const setStatusBarAnimation = async () => {
## Configuration
| Prop | Type | Description | Default |
| ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ |
| **`insetsHandling`** | <code>string</code> | Specifies how to handle problematic insets on Android. This option is only supported on Android.<br>`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.<br>`disable` = Disable all inset handling. | <code>css</code> |
| **`insetsHandling`** | <code>string</code> | Specifies how to handle problematic insets on Android. This option is only supported on Android.<br>`css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.<br>`disable` = Disable CSS variables injection. | <code>css</code> |
| **`style`** | <code>string</code> | The style of the text and icons of the system bars. | <code>DEFAULT</code> |
| **`hidden`** | <code>boolean</code> | Hide the system bars on start. | <code>false</code> |
| **`animation`** | <code>string</code> | The type of status bar animation used when showing or hiding. This option is only supported on iOS. | <code>FADE</code> |
Expand Down
Loading