Skip to content
256 changes: 228 additions & 28 deletions src/android/StatusBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@
*/
package org.apache.cordova.statusbar;

import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
Expand All @@ -41,14 +48,19 @@
public class StatusBar extends CordovaPlugin {
private static final String TAG = "StatusBar";

// Action constants
private static final String ACTION_HIDE = "hide";
private static final String ACTION_SHOW = "show";
private static final String ACTION_READY = "_ready";
private static final String ACTION_BACKGROUND_COLOR_BY_HEX_STRING = "backgroundColorByHexString";
private static final String ACTION_OVERLAYS_WEB_VIEW = "overlaysWebView";
private static final String ACTION_STYLE_DEFAULT = "styleDefault";
private static final String ACTION_STYLE_LIGHT_CONTENT = "styleLightContent";
private static final String ACTION_GET_NAVIGATION_BAR_HEIGHT = "getNavigationBarHeight";
private static final String ACTION_GET_STATUS_BAR_HEIGHT = "getStatusBarHeight";
private static final String ACTION_GET_TOTAL_SCREEN_HEIGHT = "getTotalScreenHeight";

// Style constants
private static final String STYLE_DEFAULT = "default";
private static final String STYLE_LIGHT_CONTENT = "lightcontent";

Expand Down Expand Up @@ -107,31 +119,11 @@ public boolean execute(final String action, final CordovaArgs args, final Callba
return true;

case ACTION_SHOW:
activity.runOnUiThread(() -> {
int uiOptions = window.getDecorView().getSystemUiVisibility();
uiOptions &= ~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
uiOptions &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;

window.getDecorView().setSystemUiVisibility(uiOptions);

// CB-11197 We still need to update LayoutParams to force status bar
// to be hidden when entering e.g. text fields
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
});
activity.runOnUiThread(() -> showStatusBar());
return true;

case ACTION_HIDE:
activity.runOnUiThread(() -> {
int uiOptions = window.getDecorView().getSystemUiVisibility()
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN;

window.getDecorView().setSystemUiVisibility(uiOptions);

// CB-11197 We still need to update LayoutParams to force status bar
// to be hidden when entering e.g. text fields
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
});
activity.runOnUiThread(() -> hideStatusBar());
return true;

case ACTION_BACKGROUND_COLOR_BY_HEX_STRING:
Expand Down Expand Up @@ -162,11 +154,80 @@ public boolean execute(final String action, final CordovaArgs args, final Callba
activity.runOnUiThread(() -> setStatusBarStyle(STYLE_LIGHT_CONTENT));
return true;

case ACTION_GET_NAVIGATION_BAR_HEIGHT:
case ACTION_GET_STATUS_BAR_HEIGHT:
case ACTION_GET_TOTAL_SCREEN_HEIGHT:
handleHeightRequests(action, callbackContext);
return true;

default:
return false;
}
}

/**
* Handle all height-related requests
*/
private void handleHeightRequests(String action, CallbackContext callbackContext) {
activity.runOnUiThread(() -> {
int height;
String logMessage;

switch (action) {
case ACTION_GET_NAVIGATION_BAR_HEIGHT:
height = getNavigationBarHeight();
logMessage = "Navigation bar height";
break;
case ACTION_GET_STATUS_BAR_HEIGHT:
height = getStatusBarHeight();
logMessage = "Status bar height";
break;
case ACTION_GET_TOTAL_SCREEN_HEIGHT:
height = getTotalScreenHeight();
logMessage = "Total screen height";
break;
default:
return;
}

LOG.d(TAG, logMessage + ": " + height);
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, height));
});
}

/**
* Show the status bar
*/
private void showStatusBar() {
int uiOptions = window.getDecorView().getSystemUiVisibility();
uiOptions &= ~View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
uiOptions &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;

window.getDecorView().setSystemUiVisibility(uiOptions);

// CB-11197 We still need to update LayoutParams to force status bar
// to be hidden when entering e.g. text fields
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}

/**
* Hide the status bar
*/
private void hideStatusBar() {
int uiOptions = window.getDecorView().getSystemUiVisibility()
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN;

window.getDecorView().setSystemUiVisibility(uiOptions);

// CB-11197 We still need to update LayoutParams to force status bar
// to be hidden when entering e.g. text fields
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}

/**
* Set the status bar background color
*/
private void setStatusBarBackgroundColor(final String colorPref) {
if (colorPref.isEmpty()) return;

Expand All @@ -183,19 +244,44 @@ private void setStatusBarBackgroundColor(final String colorPref) {
window.setStatusBarColor(color);
}

/**
* Set the status bar transparency
*/
private void setStatusBarTransparent(final boolean isTransparent) {
final Window window = cordova.getActivity().getWindow();
int visibility = isTransparent
? View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
: View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;
if (!isTransparent) {
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE);
return;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(window, false);
window.setStatusBarColor(Color.TRANSPARENT);
}
else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
View decorView = window.getDecorView();

int flags = decorView.getSystemUiVisibility();
flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
flags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
flags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
decorView.setSystemUiVisibility(flags);

window.getDecorView().setSystemUiVisibility(visibility);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);

if (isTransparent) {
window.setNavigationBarColor(Color.TRANSPARENT);
}
else {
int visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
window.getDecorView().setSystemUiVisibility(visibility);
window.setStatusBarColor(Color.TRANSPARENT);
}
}

/**
* Set the status bar style
*/
private void setStatusBarStyle(final String style) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !style.isEmpty()) {
View decorView = window.getDecorView();
Expand All @@ -210,4 +296,118 @@ private void setStatusBarStyle(final String style) {
}
}
}

private boolean isEdgeToEdge() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
View decorView = window.getDecorView();
int uiOptions = decorView.getSystemUiVisibility();

// Check for flags that indicate edge-to-edge mode
boolean hasLayoutFullscreen = (uiOptions & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;
boolean hasLayoutHideNavigation = (uiOptions & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0;
boolean hasImmersiveFlag = (uiOptions & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
|| (uiOptions & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;

// Check window flags
boolean hasTranslucentStatus = (window.getAttributes().flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0;
boolean hasTranslucentNavigation = (window.getAttributes().flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0;
boolean hasLayoutNoLimits = (window.getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0;

boolean drawSystemBarBackgrounds = (window.getAttributes().flags & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
int statusBarColor = window.getStatusBarColor();
int navBarColor = window.getNavigationBarColor();

// Check if status bar or nav bar has transparency
boolean hasTransparentBars = drawSystemBarBackgrounds &&
(Color.alpha(statusBarColor) < 255 || Color.alpha(navBarColor) < 255);

// If any of these flags are set, we're likely in edge-to-edge mode
return hasLayoutFullscreen || hasLayoutHideNavigation || hasImmersiveFlag ||
hasTranslucentStatus || hasTranslucentNavigation || hasLayoutNoLimits ||
hasTransparentBars;
}
return true;
}

/**
* Gets window insets for API 30+ devices
*/
private WindowInsets getWindowInsets() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
View decorView = window.getDecorView();
return decorView.getRootWindowInsets();
}
return null;
}

/**
* Helper method to check if the device has a navigation bar.
*
* @return true if the device has a navigation bar, false otherwise
*/
private boolean hasNavigationBar() {
Resources resources = activity.getResources();
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
return resources.getBoolean(id);
}

boolean hasMenuKey = ViewConfiguration.get(activity).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);

return !hasMenuKey && !hasBackKey;
}

/**
* Gets the bottom navigation bar height
*/
private int getNavigationBarHeight() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsets insets = getWindowInsets();
if (insets != null) {
return insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()).bottom;
}
} else if (isEdgeToEdge()) {
Resources resources = activity.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0 && hasNavigationBar()) {
return resources.getDimensionPixelSize(resourceId);
}
}
return 0;
}

/**
* Gets the top status bar height
*/
private int getStatusBarHeight() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsets insets = getWindowInsets();
if (insets != null) {
return insets.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars()).top;
}
} else if (isEdgeToEdge()) {
Resources resources = activity.getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
}
return 0;
}

/**
* Gets the total screen height
*/
private int getTotalScreenHeight() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowManager windowManager = activity.getSystemService(WindowManager.class);
WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
return metrics.getBounds().height();
} else {
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.heightPixels;
}
}
}
5 changes: 4 additions & 1 deletion src/browser/StatusBarProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ module.exports = {
backgroundColorByHexString: notSupported,
hide: notSupported,
show: notSupported,
_ready: notSupported
_ready: notSupported,
getNavigationBarHeight: notSupported,
getStatusBarHeight: notSupported,
getTotalScreenHeight: notSupported
};

require('cordova/exec/proxy').add('StatusBar', module.exports);
10 changes: 7 additions & 3 deletions src/ios/CDVStatusBar.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Expand Down Expand Up @@ -42,7 +42,11 @@

- (void) hide:(CDVInvokedUrlCommand*)command;
- (void) show:(CDVInvokedUrlCommand*)command;

- (void) _ready:(CDVInvokedUrlCommand*)command;

- (void) getStatusBarHeight:(CDVInvokedUrlCommand*)command;
- (void) getNavigationBarHeight:(CDVInvokedUrlCommand*)command;
- (void) getTotalScreenHeight:(CDVInvokedUrlCommand*)command;

@end
Loading