Skip to content

Commit 4e99598

Browse files
authored
fix(SystemBars): use native safe area insets on Android (ionic-team#8384)
1 parent 003099a commit 4e99598

File tree

3 files changed

+67
-33
lines changed

3 files changed

+67
-33
lines changed

android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import android.os.Build;
88
import android.util.TypedValue;
99
import android.view.View;
10-
import android.view.ViewGroup;
1110
import android.view.Window;
1211
import android.webkit.JavascriptInterface;
1312
import android.webkit.WebView;
@@ -36,6 +35,11 @@ public class SystemBars extends Plugin {
3635
static final String INSETS_HANDLING_CSS = "css";
3736
static final String INSETS_HANDLING_DISABLE = "disable";
3837

38+
// https://issues.chromium.org/issues/40699457
39+
private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_FIX = 140;
40+
// https://issues.chromium.org/issues/457682720
41+
private static final int WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX = 144;
42+
3943
static final String viewportMetaJSFunction = """
4044
function capacitorSystemBarsCheckMetaViewport() {
4145
const meta = document.querySelectorAll("meta[name=viewport]");
@@ -94,7 +98,7 @@ private void initSystemBars() {
9498
}
9599

96100
initWindowInsetsListener();
97-
initSafeAreaInsets();
101+
initSafeAreaCSSVariables();
98102

99103
getBridge().executeOnMainThread(() -> {
100104
setStyle(style, "");
@@ -157,7 +161,7 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) {
157161
return Insets.of(safeArea.left, safeArea.top, safeArea.right, safeArea.bottom);
158162
}
159163

160-
private void initSafeAreaInsets() {
164+
private void initSafeAreaCSSVariables() {
161165
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
162166
View v = (View) this.getBridge().getWebView().getParent();
163167
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v);
@@ -169,41 +173,57 @@ private void initSafeAreaInsets() {
169173
}
170174

171175
private void initWindowInsetsListener() {
172-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) {
173-
ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
174-
boolean hasBrokenWebViewVersion = getWebViewMajorVersion() <= 139;
176+
ViewCompat.setOnApplyWindowInsetsListener((View) getBridge().getWebView().getParent(), (v, insets) -> {
177+
boolean shouldPassthroughInsets = getWebViewMajorVersion() >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover;
178+
179+
Insets systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
180+
Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
181+
boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
175182

176-
if (hasViewportCover) {
183+
if (shouldPassthroughInsets) {
184+
// We need to correct for a possible shown IME
185+
v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
186+
187+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
177188
Insets safeAreaInsets = calcSafeAreaInsets(insets);
178189
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
179190
}
180191

181-
if (hasBrokenWebViewVersion) {
182-
if (hasViewportCover && v.hasWindowFocus() && v.isShown()) {
183-
boolean keyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
184-
if (keyboardVisible) {
185-
Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime());
186-
setViewMargins(v, Insets.of(0, 0, 0, imeInsets.bottom));
187-
} else {
188-
setViewMargins(v, Insets.NONE);
189-
}
190-
191-
return WindowInsetsCompat.CONSUMED;
192-
}
193-
}
192+
return new WindowInsetsCompat.Builder(insets)
193+
.setInsets(
194+
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
195+
Insets.of(
196+
systemBarsInsets.left,
197+
systemBarsInsets.top,
198+
systemBarsInsets.right,
199+
getBottomInset(systemBarsInsets, keyboardVisible)
200+
)
201+
)
202+
.build();
203+
}
194204

195-
return insets;
196-
});
197-
}
198-
}
205+
// We need to correct for a possible shown IME
206+
v.setPadding(
207+
systemBarsInsets.left,
208+
systemBarsInsets.top,
209+
systemBarsInsets.right,
210+
keyboardVisible ? imeInsets.bottom : systemBarsInsets.bottom
211+
);
212+
213+
// Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
214+
// So we have to explicitly set insets to `0`
215+
// See: https://issues.chromium.org/issues/461332423
216+
WindowInsetsCompat newInsets = new WindowInsetsCompat.Builder(insets)
217+
.setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0))
218+
.build();
219+
220+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
221+
Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
222+
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
223+
}
199224

200-
private void setViewMargins(View v, Insets insets) {
201-
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
202-
mlp.leftMargin = insets.left;
203-
mlp.bottomMargin = insets.bottom;
204-
mlp.rightMargin = insets.right;
205-
mlp.topMargin = insets.top;
206-
v.setLayoutParams(mlp);
225+
return newInsets;
226+
});
207227
}
208228

209229
private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
@@ -305,4 +325,18 @@ private Integer getWebViewMajorVersion() {
305325

306326
return 0;
307327
}
328+
329+
private int getBottomInset(Insets systemBarsInsets, boolean keyboardVisible) {
330+
if (getWebViewMajorVersion() < WEBVIEW_VERSION_WITH_SAFE_AREA_KEYBOARD_FIX) {
331+
// This is a workaround for webview versions that have a bug
332+
// that causes the bottom inset to be incorrect if the IME is visible
333+
// See: https://issues.chromium.org/issues/457682720
334+
335+
if (keyboardVisible) {
336+
return 0;
337+
}
338+
}
339+
340+
return systemBarsInsets.bottom;
341+
}
308342
}

cli/src/declarations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ export interface PluginsConfig {
713713
*
714714
* `css` = Injects CSS variables (`--safe-area-inset-*`) containing correct safe area inset values into the webview.
715715
*
716-
* `disable` = Disable all inset handling.
716+
* `disable` = Disable CSS variables injection.
717717
*
718718
* @default "css"
719719
*/

core/system-bars.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const setStatusBarAnimation = async () => {
7373
## Configuration
7474
| Prop | Type | Description | Default |
7575
| ------------- | -------------------- | ------------------------------------------------------------------------- | ------------------ |
76-
| **`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> |
76+
| **`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> |
7777
| **`style`** | <code>string</code> | The style of the text and icons of the system bars. | <code>DEFAULT</code> |
7878
| **`hidden`** | <code>boolean</code> | Hide the system bars on start. | <code>false</code> |
7979
| **`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> |

0 commit comments

Comments
 (0)