ionic-team / ionic-team/capacitor

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

Open
#8,603 1 comment 0 reactions 0 assignees View on GitHub
triage
Dominant language
TypeScript
Stars
16.7k
Forks
1.3k
Avg merge
3d 15h
Merged PRs (30d)
10

Description

### 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
capacitor.config: "SystemBars": { "initialViewportFitValueHint": "cover" } (insetsHandling default "css")
```

### Platforms Affected

- [ ] iOS
- [x] 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.

Contributor guide

Open the contributing guide

Research direction

Start at the Android SystemBars initWindowInsetsListener, especially the WindowInsetsCompat.Type.systemBars() handling on API < 30. Reproduce with EdgeToEdge.enable() on Android 9 or 10, then verify that visible navigation bars report their stable bottom inset while hidden bars remain unpadded.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, java
Domain
mobile-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.