#8603·capacitor

[Bug]: SystemBars reports a zero bottom inset on API < 30 when the app uses EdgeToEdge.enable() (content renders under the navigation bar)

Author: seraviferCreated Sep 15, 2026Updated Sep 17, 2026
Labelstriage

Capacitor Version

@capacitor/android 8.5.2
@capacitor/core 8.5.2
@capacitor/keyboard 8.0.5
@capacitor/splash-screen 8.0.2

Other API Details

Android: compileSdk/targetSdk 36, minSdk 26
Affected device: PAX A920Pro, Android 10 (API 29), Android System WebView 147.0.7727.137
Also reported by our users: Saturn1000F2, Android 9 (API 28), WebView 135
Not affected: same app on Android 11+ (API 30+), any WebView
The activity calls androidx `EdgeToEdge.enable(this)` in onCreate
Page declares <meta name="viewport" content="... viewport-fit=cover">
capacitor.config: "SystemBars": { "initialViewportFitValueHint": "cover" } (insetsHandling default "css")

Platforms Affected

  • iOS
  • Android
  • Web

Current Behavior

On API < 30, when the activity enables edge-to-edge with androidx EdgeToEdge.enable(), SystemBars reports a bottom inset of 0 while the navigation bar is visible and drawn over the window. Web content therefore renders underneath the navigation bar. The top inset is unaffected.

Measured on a PAX A920Pro (Android 10, 720x1440 @ 320dpi, dpr 2), page read through remote debugging:

--safe-area-inset-top: 24px      env(safe-area-inset-top): 24px
--safe-area-inset-bottom: 0px    env(safe-area-inset-bottom): 0px
innerHeight: 720  (= full screen height, window is edge-to-edge)

while dumpsys window windows shows the navigation bar is present and 48dp tall:

Window{... NavigationBar0}: mFrame=[0,1344][720,1440]     # 96px = 48dp
Window{... StatusBar}:      mFrame=[0,0][720,48]          # 48px = 24dp
Window{... MainActivity}:   mFrame=[0,0][720,1440]        # window covers both bars
  vsysui=LAYOUT_STABLE LAYOUT_HIDE_NAVIGATION LAYOUT_FULLSCREEN LIGHT_STATUS_BAR LIGHT_NAVIGATION_BAR
  mSystemUiVisibility=0x2710

Other apps on the same device get mContentInsets=[0,48][0,96], so the navigation bar is a normal inset provider on this ROM.

The flags above are what EdgeToEdge.enable() sets on API < 30 (SYSTEM_UI_FLAG_LAYOUT_STABLE | LAYOUT_HIDE_NAVIGATION | LAYOUT_FULLSCREEN). LAYOUT_HIDE_NAVIGATION tells the platform the window lays itself out under the navigation bar, so the navigation bar is dropped from the window's system-window insets. initWindowInsetsListener reads exactly those:

java
View view = getActivity().getWindow().getDecorView();

ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> {
    Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());

On API < 30 WindowInsetsCompat.getInsets() resolves to the system-window insets, which is where the zero comes from; on API 30+ it maps to the real WindowInsets.getInsets(Type) API, which the legacy layout flags do not affect — hence the version split.

Both branches of the listener are affected, so the symptom does not depend on the WebView version: with WebView >= 140 the zero is passed through to env(safe-area-inset-bottom), and with WebView < 140 the decor view is padded by 0 and --safe-area-inset-bottom: 0px is injected.

We started seeing this after upgrading 8.4.2 -> 8.5.2. In 8.4.x the listener was attached to the WebView's parent rather than the decor view (changed in #8535), which we believe is why the app was not exposed to the window-level layout flags before.

Expected Behavior

SystemBars reports the navigation bar inset whenever the navigation bar is visible, independent of the legacy SYSTEM_UI_FLAG_LAYOUT_* flags that EdgeToEdge.enable() sets on API < 30 — the same values the app gets on API 30+.

WindowInsetsCompat.getInsetsIgnoringVisibility() returns the correct value here: for NAVIGATION_BARS on API < 30 it resolves to the stable insets, which the layout flags do not zero. Guarding it with isVisible() keeps apps that genuinely hide the bars (immersive/fullscreen) unpadded:

java
Insets systemBarsInsets = Build.VERSION.SDK_INT < Build.VERSION_CODES.R && insets.isVisible(WindowInsetsCompat.Type.navigationBars())
    ? insets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout())
    : insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());

As a workaround we now call EdgeToEdge.enable() only on API 30+, which removes the flags and fixes the overlap, at the cost of edge-to-edge rendering (and therefore the system bar colours) on older devices.

Additional Information

Possibly related to #8601, which is a different symptom in the same listener on API < 30.