11package com .getcapacitor .plugin ;
22
3+ import android .annotation .SuppressLint ;
34import android .content .Context ;
45import android .content .pm .PackageInfo ;
56import android .content .res .Configuration ;
@@ -59,6 +60,8 @@ function capacitorSystemBarsCheckMetaViewport() {
5960 private String currentStatusBarStyle = STYLE_DEFAULT ;
6061 private String currentGestureBarStyle = STYLE_DEFAULT ;
6162
63+ private boolean navBarVisible = true ;
64+
6265 @ Override
6366 public void load () {
6467 getBridge ().getWebView ().addJavascriptInterface (this , "CapacitorSystemBarsAndroidInterface" );
@@ -76,7 +79,15 @@ protected void handleOnStart() {
7679 @ Override
7780 public void onPageCommitVisible (WebView view , String url ) {
7881 super .onPageCommitVisible (view , url );
79- getBridge ().getWebView ().requestApplyInsets ();
82+ View parentView = (View ) getBridge ().getWebView ().getParent ();
83+ ViewCompat .requestApplyInsets (parentView );
84+ if (insetHandlingEnabled ) {
85+ WindowInsetsCompat rootInsets = ViewCompat .getRootWindowInsets (parentView );
86+ if (rootInsets != null ) {
87+ Insets safeArea = calcSafeAreaInsets (rootInsets );
88+ injectSafeAreaCSS (safeArea .top , safeArea .right , safeArea .bottom , safeArea .left );
89+ }
90+ }
8091 }
8192 }
8293 );
@@ -103,8 +114,17 @@ private void initSystemBars() {
103114 initSafeAreaCSSVariables ();
104115
105116 getBridge ().executeOnMainThread (() -> {
117+ Window window = getActivity ().getWindow ();
118+ WindowCompat .setDecorFitsSystemWindows (window , false );
119+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
120+ window .setNavigationBarColor (android .graphics .Color .TRANSPARENT );
121+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
122+ window .setNavigationBarContrastEnforced (false );
123+ }
124+ }
106125 setStyle (style , "" );
107126 setHidden (hidden , "" );
127+ ViewCompat .requestApplyInsets ((View ) getBridge ().getWebView ().getParent ());
108128 });
109129 }
110130
@@ -144,43 +164,82 @@ public void setAnimation(final PluginCall call) {
144164 call .resolve ();
145165 }
146166
167+ @ Override
168+ protected void handleOnResume () {
169+ super .handleOnResume ();
170+ getBridge ().executeOnMainThread (() -> {
171+ Window window = getActivity ().getWindow ();
172+ WindowCompat .setDecorFitsSystemWindows (window , false );
173+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
174+ window .setNavigationBarColor (android .graphics .Color .TRANSPARENT );
175+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
176+ window .setNavigationBarContrastEnforced (false );
177+ }
178+ }
179+ setStyle (currentGestureBarStyle , BAR_GESTURE_BAR );
180+ setStyle (currentStatusBarStyle , BAR_STATUS_BAR );
181+ ViewCompat .requestApplyInsets ((View ) getBridge ().getWebView ().getParent ());
182+ });
183+ }
184+
147185 @ JavascriptInterface
148186 public void onDOMReady () {
149187 getActivity ().runOnUiThread (() -> {
150188 this .bridge .getWebView ().evaluateJavascript (viewportMetaJSFunction , (res ) -> {
151189 hasViewportCover = res .equals ("true" );
152-
153- getBridge ().getWebView ().requestApplyInsets ();
190+ ViewCompat .requestApplyInsets ((View ) getBridge ().getWebView ().getParent ());
154191 });
155192 });
156193 }
157194
158195 private Insets calcSafeAreaInsets (WindowInsetsCompat insets ) {
159196 Insets safeArea = insets .getInsets (WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout ());
160- if (insets .isVisible (WindowInsetsCompat .Type .ime ())) {
197+
198+ int bottom = safeArea .bottom ;
199+
200+ if (bottom == 0 && Build .VERSION .SDK_INT < Build .VERSION_CODES .R && safeArea .left == 0 && safeArea .right == 0 ) {
201+ // skip if nav bar is on a side (landscape)
202+ bottom = getNavBarHeightFromResources ();
203+ }
204+
205+ boolean imeVisible = Build .VERSION .SDK_INT >= Build .VERSION_CODES .O
206+ ? insets .isVisible (WindowInsetsCompat .Type .ime ())
207+ : insets .getInsets (WindowInsetsCompat .Type .ime ()).bottom > 0 ;
208+
209+ if (imeVisible ) {
161210 return Insets .of (safeArea .left , safeArea .top , safeArea .right , 0 );
162211 }
163- return Insets .of (safeArea .left , safeArea .top , safeArea .right , safeArea . bottom );
212+ return Insets .of (safeArea .left , safeArea .top , safeArea .right , bottom );
164213 }
165214
166- private void initSafeAreaCSSVariables () {
167- WindowInsetsCompat insets ;
215+ // Only reached on API < 30 when the window reports no inset on any edge, so the
216+ // choice is this approximation or a hardcoded 0. Lint rejects the internal
217+ // resource because OEMs may change it; a wrong height still beats no inset.
218+ @ SuppressLint ({ "InternalInsetResource" , "DiscouragedApi" })
219+ private int getNavBarHeightFromResources () {
220+ if (!navBarVisible ) return 0 ;
221+ android .content .res .Resources res = getActivity ().getResources ();
222+ int heightId = res .getIdentifier ("navigation_bar_height" , "dimen" , "android" );
223+ return heightId > 0 ? res .getDimensionPixelSize (heightId ) : 0 ;
224+ }
168225
169- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
226+ private void initSafeAreaCSSVariables () {
227+ if (insetHandlingEnabled ) {
170228 View v = (View ) this .getBridge ().getWebView ().getParent ();
171- insets = ViewCompat .getRootWindowInsets (v );
172- } else {
173- insets = WindowInsetsCompat .CONSUMED ;
174- }
175-
176- if (insets != null ) {
177- Insets safeAreaInsets = calcSafeAreaInsets (insets );
178- injectSafeAreaCSS (safeAreaInsets .top , safeAreaInsets .right , safeAreaInsets .bottom , safeAreaInsets .left );
229+ WindowInsetsCompat insets = ViewCompat .getRootWindowInsets (v );
230+ if (insets != null ) {
231+ Insets safeAreaInsets = calcSafeAreaInsets (insets );
232+ injectSafeAreaCSS (safeAreaInsets .top , safeAreaInsets .right , safeAreaInsets .bottom , safeAreaInsets .left );
233+ }
179234 }
180235 }
181236
182237 private void initWindowInsetsListener () {
183238 ViewCompat .setOnApplyWindowInsetsListener ((View ) getBridge ().getWebView ().getParent (), (v , insets ) -> {
239+ // getRootWindowInsets() bypasses AppCompat's intermediate view consuming the bottom inset on API < 30
240+ WindowInsetsCompat rawInsets = ViewCompat .getRootWindowInsets (v );
241+ WindowInsetsCompat safeAreaSource = (rawInsets != null ) ? rawInsets : insets ;
242+
184243 boolean shouldPassthroughInsets = getWebViewMajorVersion () >= WEBVIEW_VERSION_WITH_SAFE_AREA_FIX && hasViewportCover ;
185244
186245 Insets systemBarsInsets = insets .getInsets (WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout ());
@@ -191,8 +250,10 @@ private void initWindowInsetsListener() {
191250 // We need to correct for a possible shown IME
192251 v .setPadding (0 , 0 , 0 , keyboardVisible ? imeInsets .bottom : 0 );
193252
194- Insets safeAreaInsets = calcSafeAreaInsets (insets );
195- injectSafeAreaCSS (safeAreaInsets .top , safeAreaInsets .right , safeAreaInsets .bottom , safeAreaInsets .left );
253+ if (hasViewportCover && insetHandlingEnabled ) {
254+ Insets safeAreaInsets = calcSafeAreaInsets (safeAreaSource );
255+ injectSafeAreaCSS (safeAreaInsets .top , safeAreaInsets .right , safeAreaInsets .bottom , safeAreaInsets .left );
256+ }
196257
197258 return new WindowInsetsCompat .Builder (insets )
198259 .setInsets (
@@ -207,15 +268,7 @@ private void initWindowInsetsListener() {
207268 .build ();
208269 }
209270
210- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .VANILLA_ICE_CREAM ) {
211- // We need to correct for a possible shown IME
212- v .setPadding (
213- systemBarsInsets .left ,
214- systemBarsInsets .top ,
215- systemBarsInsets .right ,
216- keyboardVisible ? imeInsets .bottom : systemBarsInsets .bottom
217- );
218- }
271+ v .setPadding (0 , 0 , 0 , keyboardVisible ? imeInsets .bottom : 0 );
219272
220273 // Returning `WindowInsetsCompat.CONSUMED` breaks recalculation of safe area insets
221274 // So we have to explicitly set insets to `0`
@@ -224,8 +277,10 @@ private void initWindowInsetsListener() {
224277 .setInsets (WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (), Insets .of (0 , 0 , 0 , 0 ))
225278 .build ();
226279
227- Insets safeAreaInsets = calcSafeAreaInsets (newInsets );
228- injectSafeAreaCSS (safeAreaInsets .top , safeAreaInsets .right , safeAreaInsets .bottom , safeAreaInsets .left );
280+ if (insetHandlingEnabled ) {
281+ Insets safeAreaInsets = calcSafeAreaInsets (safeAreaSource );
282+ injectSafeAreaCSS (safeAreaInsets .top , safeAreaInsets .right , safeAreaInsets .bottom , safeAreaInsets .left );
283+ }
229284
230285 return newInsets ;
231286 });
@@ -264,19 +319,20 @@ private void injectSafeAreaCSS(int top, int right, int bottom, int left) {
264319 }
265320
266321 private void setStyle (String style , String bar ) {
322+ String requestedStyle = style ;
267323 if (style .equals (STYLE_DEFAULT )) {
268324 style = getStyleForTheme ();
269325 }
270326
271327 Window window = getActivity ().getWindow ();
272328 WindowInsetsControllerCompat windowInsetsControllerCompat = WindowCompat .getInsetsController (window , window .getDecorView ());
273329 if (bar .isEmpty () || bar .equals (BAR_STATUS_BAR )) {
274- currentStatusBarStyle = style ;
330+ currentStatusBarStyle = requestedStyle ;
275331 windowInsetsControllerCompat .setAppearanceLightStatusBars (!style .equals (STYLE_DARK ));
276332 }
277333
278334 if (bar .isEmpty () || bar .equals (BAR_GESTURE_BAR )) {
279- currentGestureBarStyle = style ;
335+ currentGestureBarStyle = requestedStyle ;
280336 windowInsetsControllerCompat .setAppearanceLightNavigationBars (!style .equals (STYLE_DARK ));
281337 }
282338
@@ -293,6 +349,7 @@ private void setHidden(boolean hide, String bar) {
293349 }
294350 if (bar .isEmpty () || bar .equals (BAR_GESTURE_BAR )) {
295351 windowInsetsControllerCompat .hide (WindowInsetsCompat .Type .navigationBars ());
352+ navBarVisible = false ;
296353 }
297354 return ;
298355 }
@@ -302,6 +359,7 @@ private void setHidden(boolean hide, String bar) {
302359 }
303360 if (bar .isEmpty () || bar .equals (BAR_GESTURE_BAR )) {
304361 windowInsetsControllerCompat .show (WindowInsetsCompat .Type .navigationBars ());
362+ navBarVisible = true ;
305363 }
306364 }
307365
0 commit comments