From 08bfc79574e3b74dfd54e52e71502197f9925036 Mon Sep 17 00:00:00 2001 From: Erisu Date: Tue, 17 Jun 2025 10:17:16 +0900 Subject: [PATCH 01/11] feat: support previous non-e2e (add FrameLayout wrapper) --- .../org/apache/cordova/CordovaActivity.java | 63 ++++++++++++++++--- lib/prepare.js | 20 ------ 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index f0ae6e1cb..57b20641c 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -31,7 +31,9 @@ Licensed to the Apache Software Foundation (ASF) under one import android.content.res.Configuration; import android.graphics.Color; import android.media.AudioManager; +import android.os.Build; import android.os.Bundle; +import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -42,7 +44,10 @@ Licensed to the Apache Software Foundation (ASF) under one import android.widget.FrameLayout; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; import androidx.core.splashscreen.SplashScreen; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; /** * This class is the main Android activity that represents the Cordova @@ -184,26 +189,70 @@ protected void loadConfig() { //Suppressing warnings in AndroidStudio @SuppressWarnings({"deprecation", "ResourceType"}) protected void createViews() { - //Why are we setting a constant as the ID? This should be investigated - appView.getView().setId(100); - appView.getView().setLayoutParams(new FrameLayout.LayoutParams( + // Root FrameLayout + FrameLayout rootLayout = new FrameLayout(this); + rootLayout.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT)); + ViewGroup.LayoutParams.MATCH_PARENT + )); - setContentView(appView.getView()); + // WebView + View webView = appView.getView(); + webView.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + )); + + // Enable/Disable AndroidEdgeToEdge (Supported in SDK >= 35) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM + && !preferences.getBoolean("AndroidEdgeToEdge", false) + ) { + // Create StatusBar view that will overlay the top inset + View statusBarView = new View(this); + statusBarView.setTag("statusBarView"); + + // Add statusBarView to root layout. + rootLayout.addView(statusBarView); + + // Handle Window Insets + ViewCompat.setOnApplyWindowInsetsListener(rootLayout, (v, insets) -> { + Insets bars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() + | WindowInsetsCompat.Type.displayCutout() + ); + + // Update the WebView's Margin LayoutParams + FrameLayout.LayoutParams newParams = (FrameLayout.LayoutParams) webView.getLayoutParams(); + newParams.setMargins(bars.left, bars.top, bars.right, bars.bottom); + webView.setLayoutParams(newParams); + + // Position the status bar view to overlay the top inset areas + statusBarView.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + bars.top, + Gravity.TOP + )); + + return WindowInsetsCompat.CONSUMED; + }); + + ViewCompat.requestApplyInsets(rootLayout); + } if (preferences.contains("BackgroundColor")) { try { int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK); // Background of activity: - appView.getView().setBackgroundColor(backgroundColor); + webView.setBackgroundColor(backgroundColor); } catch (NumberFormatException e){ e.printStackTrace(); } } - appView.getView().requestFocusFromTouch(); + rootLayout.addView(webView); + setContentView(rootLayout); + webView.requestFocusFromTouch(); } /** diff --git a/lib/prepare.js b/lib/prepare.js index 4a294c8af..2ca2fd31c 100644 --- a/lib/prepare.js +++ b/lib/prepare.js @@ -382,26 +382,6 @@ function updateProjectTheme (platformConfig, locations) { const themes = xmlHelpers.parseElementtreeSync(locations.themes); const splashScreenTheme = themes.find('style[@name="Theme.App.SplashScreen"]'); - // Update edge-to-edge settings in app theme. - let hasE2E = false; // default case - - const preferenceE2E = platformConfig.getPreference('AndroidEdgeToEdge', this.platform); - if (!preferenceE2E) { - events.emit('verbose', 'The preference name "AndroidEdgeToEdge" was not set. Defaulting to "false".'); - } else { - const hasInvalidPreferenceE2E = preferenceE2E !== 'true' && preferenceE2E !== 'false'; - if (hasInvalidPreferenceE2E) { - events.emit('verbose', 'Preference name "AndroidEdgeToEdge" has an invalid value. Valid values are "true" or "false". Defaulting to "false"'); - } - hasE2E = hasInvalidPreferenceE2E ? false : preferenceE2E === 'true'; - } - - const optOutE2EKey = 'android:windowOptOutEdgeToEdgeEnforcement'; - const optOutE2EItem = splashScreenTheme.find(`item[@name="${optOutE2EKey}"]`); - const optOutE2EValue = !hasE2E ? 'true' : 'false'; - optOutE2EItem.text = optOutE2EValue; - events.emit('verbose', `Updating theme item "${optOutE2EKey}" with value "${optOutE2EValue}"`); - let splashBg = platformConfig.getPreference('AndroidWindowSplashScreenBackground', this.platform); if (!splashBg) { splashBg = platformConfig.getPreference('SplashScreenBackgroundColor', this.platform); From 666587bf3724a7294959539ada3709ed722d6820 Mon Sep 17 00:00:00 2001 From: Erisu Date: Tue, 17 Jun 2025 10:57:10 +0900 Subject: [PATCH 02/11] feat: implement internal SystemBar plugin --- .../org/apache/cordova/ConfigXmlParser.java | 8 + .../org/apache/cordova/CordovaActivity.java | 11 - .../apache/cordova/SplashScreenPlugin.java | 3 + .../org/apache/cordova/SystemBarPlugin.java | 225 ++++++++++++++++++ 4 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 framework/src/org/apache/cordova/SystemBarPlugin.java diff --git a/framework/src/org/apache/cordova/ConfigXmlParser.java b/framework/src/org/apache/cordova/ConfigXmlParser.java index 0b92e96b6..6e10b528a 100644 --- a/framework/src/org/apache/cordova/ConfigXmlParser.java +++ b/framework/src/org/apache/cordova/ConfigXmlParser.java @@ -77,6 +77,14 @@ public void parse(Context action) { ) ); + pluginEntries.add( + new PluginEntry( + SystemBarPlugin.PLUGIN_NAME, + "org.apache.cordova.SystemBarPlugin", + true + ) + ); + pluginEntries.add( new PluginEntry( SplashScreenPlugin.PLUGIN_NAME, diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index 57b20641c..ecc0787ab 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -239,17 +239,6 @@ protected void createViews() { ViewCompat.requestApplyInsets(rootLayout); } - if (preferences.contains("BackgroundColor")) { - try { - int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK); - // Background of activity: - webView.setBackgroundColor(backgroundColor); - } - catch (NumberFormatException e){ - e.printStackTrace(); - } - } - rootLayout.addView(webView); setContentView(rootLayout); webView.requestFocusFromTouch(); diff --git a/framework/src/org/apache/cordova/SplashScreenPlugin.java b/framework/src/org/apache/cordova/SplashScreenPlugin.java index 8f02d5219..ea1ea8345 100644 --- a/framework/src/org/apache/cordova/SplashScreenPlugin.java +++ b/framework/src/org/apache/cordova/SplashScreenPlugin.java @@ -155,10 +155,13 @@ public void onSplashScreenExit(@NonNull SplashScreenViewProvider splashScreenVie public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); splashScreenViewProvider.remove(); + webView.getPluginManager().postMessage("updateSystemBars", null); } }).start(); } }); + } else { + webView.getPluginManager().postMessage("updateSystemBars", null); } } diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java new file mode 100644 index 000000000..f2a194e94 --- /dev/null +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -0,0 +1,225 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + 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 + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +package org.apache.cordova; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Color; +import android.os.Build; +import android.view.View; +import android.view.ViewParent; +import android.view.Window; +import android.view.WindowInsetsController; +import android.widget.FrameLayout; + +import androidx.core.content.ContextCompat; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsControllerCompat; + +public class SystemBarPlugin extends CordovaPlugin { + static final String PLUGIN_NAME = "SystemBarPlugin"; + + static final int INVALID_COLOR = -1; + + // Internal variables + private Context context; + private Resources resources; + private int overrideStatusBarBackgroundColor = INVALID_COLOR; + + + @Override + protected void pluginInitialize() { + context = cordova.getContext(); + resources = context.getResources(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + cordova.getActivity().runOnUiThread(this::updateSystemBars); + } + + @Override + public void onResume(boolean multitasking) { + super.onResume(multitasking); + cordova.getActivity().runOnUiThread(this::updateSystemBars); + } + + @Override + public Object onMessage(String id, Object data) { + if (id.equals("updateSystemBars")) { + cordova.getActivity().runOnUiThread(this::updateSystemBars); + } + return null; + } + + private void updateSystemBars() { + // Update Root View Background Color + int rootViewBackgroundColor = getPreferenceBackgroundColor(); + if (rootViewBackgroundColor == INVALID_COLOR) { + rootViewBackgroundColor = getUiModeColor(); + } + updateRootView(rootViewBackgroundColor); + + // Update StatusBar Background Color + int statusBarBackgroundColor; + if (overrideStatusBarBackgroundColor != INVALID_COLOR) { + statusBarBackgroundColor = overrideStatusBarBackgroundColor; + } else if (preferences.contains("StatusBarBackgroundColor")) { + statusBarBackgroundColor = getPreferenceStatusBarBackgroundColor(); + } else if(preferences.contains("BackgroundColor")){ + statusBarBackgroundColor = rootViewBackgroundColor; + } else { + statusBarBackgroundColor = getUiModeColor(); + } + + updateStatusBar(statusBarBackgroundColor); + } + + private void updateRootView(int bgColor) { + Window window = cordova.getActivity().getWindow(); + + // Set the root view's background color. Works on SDK 36+ + View root = cordova.getActivity().findViewById(android.R.id.content); + if (root != null) root.setBackgroundColor(bgColor); + + // Automatically set the font and icon color of the system bars based on background color. + boolean isBackgroundColorLight = isColorLight(bgColor); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowInsetsController controller = window.getInsetsController(); + if (controller != null) { + int appearance = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; + if (isBackgroundColorLight) { + controller.setSystemBarsAppearance(0, appearance); + } else { + controller.setSystemBarsAppearance(appearance, appearance); + } + } + } + WindowInsetsControllerCompat controllerCompat = WindowCompat.getInsetsController(window, window.getDecorView()); + controllerCompat.setAppearanceLightNavigationBars(isBackgroundColorLight); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Allow custom navigation bar background color for SDK 26 and greater. + window.setNavigationBarColor(bgColor); + } else { + // Force navigation bar to black for SDK 25 and less. + window.setNavigationBarColor(Color.BLACK); + } + } + + private void updateStatusBar(int bgColor) { + Window window = cordova.getActivity().getWindow(); + + if (!preferences.getBoolean("AndroidEdgeToEdge", false)) { + View statusBar = getStatusBarView(webView); + if (statusBar != null) { + statusBar.setBackgroundColor(bgColor); + } + } + + // Automatically set the font and icon color of the system bars based on background color. + boolean isStatusBarBackgroundColorLight = isColorLight(bgColor); + WindowInsetsControllerCompat controllerCompat = WindowCompat.getInsetsController(window, window.getDecorView()); + controllerCompat.setAppearanceLightStatusBars(isStatusBarBackgroundColorLight); + + // Allow custom background color for StatusBar. + window.setStatusBarColor(bgColor); + } + + private static boolean isColorLight(int color) { + double r = Color.red(color) / 255.0; + double g = Color.green(color) / 255.0; + double b = Color.blue(color) / 255.0; + double luminance = 0.299 * r + 0.587 * g + 0.114 * b; + return luminance > 0.5; + } + + private int getPreferenceStatusBarBackgroundColor() { + String colorString = preferences.getString("StatusBarBackgroundColor", null); + + int parsedColor = parseColorFromString(colorString); + if (parsedColor != INVALID_COLOR) return parsedColor; + + return getUiModeColor(); // fallback + } + + private int getPreferenceBackgroundColor() { + try { + return preferences.getInteger("BackgroundColor", INVALID_COLOR); + } catch (NumberFormatException e) { + LOG.e(PLUGIN_NAME, "Invalid background color argument. Example valid string: '0x00000000'"); + return INVALID_COLOR; + } + } + + private FrameLayout getRootLayout(CordovaWebView webView) { + ViewParent parent = webView.getView().getParent(); + if (parent instanceof FrameLayout) { + return (FrameLayout) parent; + } + + return null; + } + + private View getStatusBarView(CordovaWebView webView) { + FrameLayout rootView = getRootLayout(webView); + for (int i = 0; i < (rootView != null ? rootView.getChildCount() : 0); i++) { + View child = rootView.getChildAt(i); + Object tag = child.getTag(); + if ("statusBarView".equals(tag)) { + return child; + } + } + return null; + } + + private int getUiModeColor() { + // Hardcoded fallback values matches system ui values (R.color) which were added in SDK 34. + return isNightMode() + ? getThemeColor("cdv_background_color_dark", "#121318") + : getThemeColor("cdv_background_color_light", "#FAF8FF"); + } + + private boolean isNightMode() { + return (resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + } + + private int parseColorFromString(final String colorPref) { + if (colorPref.isEmpty()) return INVALID_COLOR; + + try { + return Color.parseColor(colorPref); + } catch (IllegalArgumentException ignore) { + LOG.e(PLUGIN_NAME, "Invalid color hex code. Valid format: #RRGGBB or #AARRGGBB"); + return INVALID_COLOR; + } + } + + @SuppressLint("DiscouragedApi") + private int getThemeColor(String colorKey, String fallbackColor) { + int colorResId = resources.getIdentifier(colorKey, "color", context.getPackageName()); + return colorResId != 0 + ? ContextCompat.getColor(context, colorResId) + : Color.parseColor(fallbackColor); + } +} From 7a510fc2b5598f2929d1aea0ff8d29a74d747256 Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 18 Jun 2025 18:06:00 +0900 Subject: [PATCH 03/11] feat: implement StatusBar plugin JS API (SystemBarPlugin) --- cordova-js-src/platform.js | 4 ++ cordova-js-src/plugin/android/statusbar.js | 71 +++++++++++++++++++ .../org/apache/cordova/CordovaActivity.java | 8 ++- .../org/apache/cordova/SystemBarPlugin.java | 66 ++++++++++++++++- 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 cordova-js-src/plugin/android/statusbar.js diff --git a/cordova-js-src/platform.js b/cordova-js-src/platform.js index b9921188e..6bdbd8070 100644 --- a/cordova-js-src/platform.js +++ b/cordova-js-src/platform.js @@ -39,6 +39,10 @@ module.exports = { // Core Splash Screen modulemapper.clobbers('cordova/plugin/android/splashscreen', 'navigator.splashscreen'); + // Attach the internal statusBar utility to window.statusbar + // see the file under plugin/android/statusbar.js + modulemapper.clobbers('cordova/plugin/android/statusbar', 'window.statusbar'); + var APP_PLUGIN_NAME = Number(cordova.platformVersion.split('.')[0]) >= 4 ? 'CoreAndroid' : 'App'; // Inject a listener for the backbutton on the document. diff --git a/cordova-js-src/plugin/android/statusbar.js b/cordova-js-src/plugin/android/statusbar.js new file mode 100644 index 000000000..e7f508738 --- /dev/null +++ b/cordova-js-src/plugin/android/statusbar.js @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * 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 + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +var exec = require('cordova/exec'); + +var statusBarVisible = true; +var statusBar = {}; + +Object.defineProperty(statusBar, 'visible', { + configurable: false, + enumerable: true, + get: function () { + if (window.StatusBar) { + // try to let the StatusBar plugin handle it + return window.StatusBar.isVisible; + } + + return statusBarVisible; + }, + set: function (value) { + if (window.StatusBar) { + // try to let the StatusBar plugin handle it + if (value) { + window.StatusBar.show(); + } else { + window.StatusBar.hide(); + } + } else { + statusBarVisible = value; + exec(null, null, 'SystemBarPlugin', 'setStatusBarVisible', [!!value]); + } + } +}); + +Object.defineProperty(statusBar, 'setBackgroundColor', { + configurable: false, + enumerable: false, + writable: false, + value: function (value) { + // Confirm if value is a valid hex code is before passing to native-side + if (!(/^#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value))) { + console.error('Invalid color hex code. Valid format: #RRGGBB or #AARRGGBB'); + return; + } + + if (window.StatusBar) { + window.StatusBar.backgroundColorByHexString(value); + } else { + exec(null, null, 'SystemBarPlugin', 'setStatusBarBackgroundColor', [value]); + } + } +}); + +module.exports = statusBar; diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index ecc0787ab..33c3a3589 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -29,7 +29,6 @@ Licensed to the Apache Software Foundation (ASF) under one import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; -import android.graphics.Color; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; @@ -221,15 +220,18 @@ protected void createViews() { | WindowInsetsCompat.Type.displayCutout() ); + boolean isStatusBarVisible = statusBarView.getVisibility() != View.GONE; + int top = isStatusBarVisible ? bars.top : 0; + // Update the WebView's Margin LayoutParams FrameLayout.LayoutParams newParams = (FrameLayout.LayoutParams) webView.getLayoutParams(); - newParams.setMargins(bars.left, bars.top, bars.right, bars.bottom); + newParams.setMargins(bars.left, top, bars.right, bars.bottom); webView.setLayoutParams(newParams); // Position the status bar view to overlay the top inset areas statusBarView.setLayoutParams(new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - bars.top, + top, Gravity.TOP )); diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index f2a194e94..00cca82b7 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -29,12 +29,17 @@ Licensed to the Apache Software Foundation (ASF) under one import android.view.ViewParent; import android.view.Window; import android.view.WindowInsetsController; +import android.view.WindowManager; import android.widget.FrameLayout; import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; +import org.json.JSONArray; +import org.json.JSONException; + public class SystemBarPlugin extends CordovaPlugin { static final String PLUGIN_NAME = "SystemBarPlugin"; @@ -72,6 +77,63 @@ public Object onMessage(String id, Object data) { return null; } + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM + && preferences.getBoolean("AndroidEdgeToEdge", false) + ) { + // Disable JS API in E2E mode (SDK >= 35) + return false; + } + + if ("setStatusBarVisible".equals(action)) { + boolean visible = args.getBoolean(0); + cordova.getActivity().runOnUiThread(() -> setStatusBarVisible(visible)); + } else if ("setStatusBarBackgroundColor".equals(action)) { + String bgColor = args.getString(0); + cordova.getActivity().runOnUiThread(() -> setStatusBarBackgroundColor(bgColor)); + } else { + return false; + } + + callbackContext.success(); + return true; + } + + private void setStatusBarVisible(final boolean visible) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + View statusBar = getStatusBarView(webView); + if (statusBar != null) { + statusBar.setVisibility(visible ? View.VISIBLE : View.GONE); + + FrameLayout rootLayout = getRootLayout(webView); + if (rootLayout != null) { + ViewCompat.requestApplyInsets(rootLayout); + } + } + } else { + Window window = cordova.getActivity().getWindow(); + int uiOptions = window.getDecorView().getSystemUiVisibility(); + int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN; + if (visible) { + uiOptions &= ~flags; + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + uiOptions |= flags; + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + window.getDecorView().setSystemUiVisibility(uiOptions); + } + } + + private void setStatusBarBackgroundColor(final String colorPref) { + int parsedColor = parseColorFromString(colorPref); + if (parsedColor == INVALID_COLOR) return; + + overrideStatusBarBackgroundColor = Color.parseColor(colorPref); + updateStatusBar(overrideStatusBarBackgroundColor); + } + private void updateSystemBars() { // Update Root View Background Color int rootViewBackgroundColor = getPreferenceBackgroundColor(); @@ -130,7 +192,9 @@ private void updateRootView(int bgColor) { private void updateStatusBar(int bgColor) { Window window = cordova.getActivity().getWindow(); - if (!preferences.getBoolean("AndroidEdgeToEdge", false)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM + && !preferences.getBoolean("AndroidEdgeToEdge", false) + ) { View statusBar = getStatusBarView(webView); if (statusBar != null) { statusBar.setBackgroundColor(bgColor); From 451fc44f6b391ea8d91e51ea7131082ffa4909c5 Mon Sep 17 00:00:00 2001 From: Erisu Date: Fri, 27 Jun 2025 12:55:20 +0900 Subject: [PATCH 04/11] feat!: force custom statusbarView for all SDKs --- .../org/apache/cordova/CordovaActivity.java | 80 ++++++++++--------- .../org/apache/cordova/SystemBarPlugin.java | 54 ++++--------- 2 files changed, 58 insertions(+), 76 deletions(-) diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index 33c3a3589..463f52439 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -46,6 +46,7 @@ Licensed to the Apache Software Foundation (ASF) under one import androidx.core.graphics.Insets; import androidx.core.splashscreen.SplashScreen; import androidx.core.view.ViewCompat; +import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; /** @@ -104,6 +105,9 @@ public class CordovaActivity extends AppCompatActivity { private SplashScreen splashScreen; + private boolean canEdgeToEdge = false; + private boolean isFullScreen = false; + /** * Called when the activity is first created. */ @@ -117,6 +121,9 @@ public void onCreate(Bundle savedInstanceState) { // need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception loadConfig(); + canEdgeToEdge = preferences.getBoolean("AndroidEdgeToEdge", false) + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM; + String logLevel = preferences.getString("loglevel", "ERROR"); LOG.setLogLevel(logLevel); @@ -131,7 +138,10 @@ public void onCreate(Bundle savedInstanceState) { LOG.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version."); preferences.set("Fullscreen", true); } - if (preferences.getBoolean("Fullscreen", false)) { + + isFullScreen = preferences.getBoolean("Fullscreen", false); + + if (isFullScreen) { // NOTE: use the FullscreenNotImmersive configuration key to set the activity in a REAL full screen // (as was the case in previous cordova versions) if (!preferences.getBoolean("FullscreenNotImmersive", false)) { @@ -188,6 +198,8 @@ protected void loadConfig() { //Suppressing warnings in AndroidStudio @SuppressWarnings({"deprecation", "ResourceType"}) protected void createViews() { + WindowCompat.setDecorFitsSystemWindows(getWindow(), false); + // Root FrameLayout FrameLayout rootLayout = new FrameLayout(this); rootLayout.setLayoutParams(new FrameLayout.LayoutParams( @@ -202,47 +214,39 @@ protected void createViews() { ViewGroup.LayoutParams.MATCH_PARENT )); - // Enable/Disable AndroidEdgeToEdge (Supported in SDK >= 35) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM - && !preferences.getBoolean("AndroidEdgeToEdge", false) - ) { - // Create StatusBar view that will overlay the top inset - View statusBarView = new View(this); - statusBarView.setTag("statusBarView"); - - // Add statusBarView to root layout. - rootLayout.addView(statusBarView); - - // Handle Window Insets - ViewCompat.setOnApplyWindowInsetsListener(rootLayout, (v, insets) -> { - Insets bars = insets.getInsets( - WindowInsetsCompat.Type.systemBars() - | WindowInsetsCompat.Type.displayCutout() - ); - - boolean isStatusBarVisible = statusBarView.getVisibility() != View.GONE; - int top = isStatusBarVisible ? bars.top : 0; - - // Update the WebView's Margin LayoutParams - FrameLayout.LayoutParams newParams = (FrameLayout.LayoutParams) webView.getLayoutParams(); - newParams.setMargins(bars.left, top, bars.right, bars.bottom); - webView.setLayoutParams(newParams); - - // Position the status bar view to overlay the top inset areas - statusBarView.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - top, - Gravity.TOP - )); - - return WindowInsetsCompat.CONSUMED; - }); + // Create StatusBar view that will overlay the top inset + View statusBarView = new View(this); + statusBarView.setTag("statusBarView"); - ViewCompat.requestApplyInsets(rootLayout); - } + // Handle Window Insets + ViewCompat.setOnApplyWindowInsetsListener(rootLayout, (v, insets) -> { + Insets bars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout() + ); + + boolean isStatusBarVisible = statusBarView.getVisibility() != View.GONE; + int top = isStatusBarVisible && !canEdgeToEdge && !isFullScreen ? bars.top : 0; + int bottom = !canEdgeToEdge && !isFullScreen ? bars.bottom : 0; + + FrameLayout.LayoutParams webViewParams = (FrameLayout.LayoutParams) webView.getLayoutParams(); + webViewParams.setMargins(bars.left, top, bars.right, bottom); + webView.setLayoutParams(webViewParams); + + FrameLayout.LayoutParams statusBarParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + top, + Gravity.TOP + ); + statusBarView.setLayoutParams(statusBarParams); + + return WindowInsetsCompat.CONSUMED; + }); rootLayout.addView(webView); + rootLayout.addView(statusBarView); + setContentView(rootLayout); + rootLayout.post(() -> ViewCompat.requestApplyInsets(rootLayout)); webView.requestFocusFromTouch(); } diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index 00cca82b7..d921b20bd 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -29,7 +29,6 @@ Licensed to the Apache Software Foundation (ASF) under one import android.view.ViewParent; import android.view.Window; import android.view.WindowInsetsController; -import android.view.WindowManager; import android.widget.FrameLayout; import androidx.core.content.ContextCompat; @@ -50,11 +49,14 @@ public class SystemBarPlugin extends CordovaPlugin { private Resources resources; private int overrideStatusBarBackgroundColor = INVALID_COLOR; + private boolean canEdgeToEdge = false; @Override protected void pluginInitialize() { context = cordova.getContext(); resources = context.getResources(); + canEdgeToEdge = preferences.getBoolean("AndroidEdgeToEdge", false) + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM; } @Override @@ -79,10 +81,7 @@ public Object onMessage(String id, Object data) { @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM - && preferences.getBoolean("AndroidEdgeToEdge", false) - ) { - // Disable JS API in E2E mode (SDK >= 35) + if(canEdgeToEdge) { return false; } @@ -101,28 +100,14 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } private void setStatusBarVisible(final boolean visible) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - View statusBar = getStatusBarView(webView); - if (statusBar != null) { - statusBar.setVisibility(visible ? View.VISIBLE : View.GONE); - - FrameLayout rootLayout = getRootLayout(webView); - if (rootLayout != null) { - ViewCompat.requestApplyInsets(rootLayout); - } - } - } else { - Window window = cordova.getActivity().getWindow(); - int uiOptions = window.getDecorView().getSystemUiVisibility(); - int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN; - if (visible) { - uiOptions &= ~flags; - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - uiOptions |= flags; - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + View statusBar = getStatusBarView(webView); + if (statusBar != null) { + statusBar.setVisibility(visible ? View.VISIBLE : View.GONE); + + FrameLayout rootLayout = getRootLayout(webView); + if (rootLayout != null) { + ViewCompat.requestApplyInsets(rootLayout); } - window.getDecorView().setSystemUiVisibility(uiOptions); } } @@ -138,7 +123,7 @@ private void updateSystemBars() { // Update Root View Background Color int rootViewBackgroundColor = getPreferenceBackgroundColor(); if (rootViewBackgroundColor == INVALID_COLOR) { - rootViewBackgroundColor = getUiModeColor(); + rootViewBackgroundColor = canEdgeToEdge ? Color.TRANSPARENT : getUiModeColor(); } updateRootView(rootViewBackgroundColor); @@ -151,7 +136,7 @@ private void updateSystemBars() { } else if(preferences.contains("BackgroundColor")){ statusBarBackgroundColor = rootViewBackgroundColor; } else { - statusBarBackgroundColor = getUiModeColor(); + statusBarBackgroundColor = canEdgeToEdge ? Color.TRANSPARENT : getUiModeColor(); } updateStatusBar(statusBarBackgroundColor); @@ -192,22 +177,15 @@ private void updateRootView(int bgColor) { private void updateStatusBar(int bgColor) { Window window = cordova.getActivity().getWindow(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM - && !preferences.getBoolean("AndroidEdgeToEdge", false) - ) { - View statusBar = getStatusBarView(webView); - if (statusBar != null) { - statusBar.setBackgroundColor(bgColor); - } + View statusBar = getStatusBarView(webView); + if (statusBar != null) { + statusBar.setBackgroundColor(bgColor); } // Automatically set the font and icon color of the system bars based on background color. boolean isStatusBarBackgroundColorLight = isColorLight(bgColor); WindowInsetsControllerCompat controllerCompat = WindowCompat.getInsetsController(window, window.getDecorView()); controllerCompat.setAppearanceLightStatusBars(isStatusBarBackgroundColorLight); - - // Allow custom background color for StatusBar. - window.setStatusBarColor(bgColor); } private static boolean isColorLight(int color) { From b0c1b962125743541dd3e54783c88cbf737842c6 Mon Sep 17 00:00:00 2001 From: Erisu Date: Sat, 28 Jun 2025 00:20:58 +0900 Subject: [PATCH 05/11] chore: various cleanup, refactors, fixes, and docs from recent changes --- .../org/apache/cordova/SystemBarPlugin.java | 121 +++++++++++++++--- 1 file changed, 102 insertions(+), 19 deletions(-) diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index d921b20bd..e4752a694 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -99,6 +99,13 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo return true; } + /** + * Allow the app to override the status bar visibility from JS API. + * If for some reason the statusBarView could not be discovered, it will silently ignore + * the change request + * + * @param visible should the status bar be visible? + */ private void setStatusBarVisible(final boolean visible) { View statusBar = getStatusBarView(webView); if (statusBar != null) { @@ -111,14 +118,29 @@ private void setStatusBarVisible(final boolean visible) { } } + /** + * Allow the app to override the status bar background color from JS API. + * If the supplied string is invalid and fails to parse, it will silently ignore + * the change request. + * + * @param colorPref hex string + */ private void setStatusBarBackgroundColor(final String colorPref) { int parsedColor = parseColorFromString(colorPref); if (parsedColor == INVALID_COLOR) return; - overrideStatusBarBackgroundColor = Color.parseColor(colorPref); + overrideStatusBarBackgroundColor = parsedColor; updateStatusBar(overrideStatusBarBackgroundColor); } + /** + * Attempt to update all system bars (status, navigation and gesture bars) in various points + * of the apps life cycle. + * For example: + * 1. Device configurations between (E.g. between dark and light mode) + * 2. User resumes the app + * 3. App transitions from SplashScreen Theme to App's Theme + */ private void updateSystemBars() { // Update Root View Background Color int rootViewBackgroundColor = getPreferenceBackgroundColor(); @@ -142,6 +164,18 @@ private void updateSystemBars() { updateStatusBar(statusBarBackgroundColor); } + /** + * Updates the root layout's background color with the supplied color int. + * It will also determine if the background color is light or dark to properly adjust the + * appearance of the navigation/gesture bar's icons so it will not clash with the background. + *

+ * System bars (navigation & gesture) on SDK 25 or lower is forced to black as the appearance + * of the fonts can not be updated. + * System bars (navigation & gesture) on SDK 26 or greater allows custom background color. + *

+ * + * @param bgColor Background color + */ private void updateRootView(int bgColor) { Window window = cordova.getActivity().getWindow(); @@ -166,14 +200,19 @@ private void updateRootView(int bgColor) { controllerCompat.setAppearanceLightNavigationBars(isBackgroundColorLight); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // Allow custom navigation bar background color for SDK 26 and greater. window.setNavigationBarColor(bgColor); } else { - // Force navigation bar to black for SDK 25 and less. window.setNavigationBarColor(Color.BLACK); } } + /** + * Updates the statusBarView background color with the supplied color int. + * It will also determine if the background color is light or dark to properly adjust the + * appearance of the status bar so the font will not clash with the background. + * + * @param bgColor Background color + */ private void updateStatusBar(int bgColor) { Window window = cordova.getActivity().getWindow(); @@ -188,6 +227,12 @@ private void updateStatusBar(int bgColor) { controllerCompat.setAppearanceLightStatusBars(isStatusBarBackgroundColorLight); } + /** + * Determines if the supplied color's appearance is light. + * + * @param color color + * @return boolean value true is returned when the color is light. + */ private static boolean isColorLight(int color) { double r = Color.red(color) / 255.0; double g = Color.green(color) / 255.0; @@ -196,6 +241,14 @@ private static boolean isColorLight(int color) { return luminance > 0.5; } + /** + * Returns the StatusBarBackgroundColor preference value. + * If the value is missing or fails to parse, it will attempt to try to guess the background + * color by extracting from the apps R.color.cdv_background_color or determine from the uiModes. + * If all fails, the color normally used in light mode is returned. + * + * @return int + */ private int getPreferenceStatusBarBackgroundColor() { String colorString = preferences.getString("StatusBarBackgroundColor", null); @@ -205,6 +258,12 @@ private int getPreferenceStatusBarBackgroundColor() { return getUiModeColor(); // fallback } + /** + * Returns the BackgroundColor preference value. + * If missing or fails to decode, it will return INVALID_COLOR (-1). + * + * @return int + */ private int getPreferenceBackgroundColor() { try { return preferences.getInteger("BackgroundColor", INVALID_COLOR); @@ -214,6 +273,12 @@ private int getPreferenceBackgroundColor() { } } + /** + * Tries to find and return the rootLayout. + * + * @param webView CordovaWebView + * @return FrameLayout|null + */ private FrameLayout getRootLayout(CordovaWebView webView) { ViewParent parent = webView.getView().getParent(); if (parent instanceof FrameLayout) { @@ -223,6 +288,12 @@ private FrameLayout getRootLayout(CordovaWebView webView) { return null; } + /** + * Tries to find and return the statusBarView. + * + * @param webView CordovaWebView + * @return View|null + */ private View getStatusBarView(CordovaWebView webView) { FrameLayout rootView = getRootLayout(webView); for (int i = 0; i < (rootView != null ? rootView.getChildCount() : 0); i++) { @@ -235,17 +306,37 @@ private View getStatusBarView(CordovaWebView webView) { return null; } + /** + * Determines the background color for status bar & root layer. + * The color will come from the app's R.color.cdv_background_color. + * If for some reason the resource is missing, it will try to fallback on the uiMode. + *

+ * The uiMode as follows. + * If night mode: "#121318" (android.R.color.system_background_dark) + * If day mode: "#FAF8FF" (android.R.color.system_background_light) + * If all fails, light mode will be returned. + *

+ * The hex values are supplied instead of "android.R.color" for backwards compatibility. + * + * @return int color + */ + @SuppressLint("DiscouragedApi") private int getUiModeColor() { - // Hardcoded fallback values matches system ui values (R.color) which were added in SDK 34. - return isNightMode() - ? getThemeColor("cdv_background_color_dark", "#121318") - : getThemeColor("cdv_background_color_light", "#FAF8FF"); - } - - private boolean isNightMode() { - return (resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + boolean isNightMode = (resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + String fallbackColor = isNightMode ? "#121318" : "#FAF8FF"; + int colorResId = resources.getIdentifier("cdv_background_color", "color", context.getPackageName()); + return colorResId != 0 + ? ContextCompat.getColor(context, colorResId) + : Color.parseColor(fallbackColor); } + /** + * Parse color string that would be provided by app developers. + * If the color string is empty or unable to parse, it will return INVALID_COLOR (-1). + * + * @param colorPref hex string value, #AARRGGBB or #RRGGBB + * @return int + */ private int parseColorFromString(final String colorPref) { if (colorPref.isEmpty()) return INVALID_COLOR; @@ -256,12 +347,4 @@ private int parseColorFromString(final String colorPref) { return INVALID_COLOR; } } - - @SuppressLint("DiscouragedApi") - private int getThemeColor(String colorKey, String fallbackColor) { - int colorResId = resources.getIdentifier(colorKey, "color", context.getPackageName()); - return colorResId != 0 - ? ContextCompat.getColor(context, colorResId) - : Color.parseColor(fallbackColor); - } } From 724d96ac893c814f7b41eef7a22187b984857eca Mon Sep 17 00:00:00 2001 From: Erisu Date: Sat, 28 Jun 2025 00:36:12 +0900 Subject: [PATCH 06/11] feat: use getComputedStyle for setBackgroundColor --- cordova-js-src/plugin/android/statusbar.js | 25 ++++++++++++---- .../org/apache/cordova/SystemBarPlugin.java | 29 ++++++++++++------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/cordova-js-src/plugin/android/statusbar.js b/cordova-js-src/plugin/android/statusbar.js index e7f508738..cbef17e88 100644 --- a/cordova-js-src/plugin/android/statusbar.js +++ b/cordova-js-src/plugin/android/statusbar.js @@ -54,16 +54,31 @@ Object.defineProperty(statusBar, 'setBackgroundColor', { enumerable: false, writable: false, value: function (value) { - // Confirm if value is a valid hex code is before passing to native-side - if (!(/^#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value))) { - console.error('Invalid color hex code. Valid format: #RRGGBB or #AARRGGBB'); + var script = document.querySelector('script[src$="cordova.js"]'); + script.style.color = value; + var rgbStr = window.getComputedStyle(script).getPropertyValue('color'); + + if (!rgbStr.match(/^rgb/)) { return; } + + var rgbVals = rgbStr.match(/\d+/g).map(function (v) { return parseInt(v, 10); }); + + if (rgbVals.length < 3) { return; + } else if (rgbVals.length === 3) { + rgbVals = [255].concat(rgbVals); } + const padRgb = (val) => val.toString(16).padStart(2, '0'); + const a = padRgb(rgbVals[0]); + const r = padRgb(rgbVals[1]); + const g = padRgb(rgbVals[2]); + const b = padRgb(rgbVals[3]); + const hexStr = '#' + a + r + g + b; + if (window.StatusBar) { - window.StatusBar.backgroundColorByHexString(value); + window.StatusBar.backgroundColorByHexString(hexStr); } else { - exec(null, null, 'SystemBarPlugin', 'setStatusBarBackgroundColor', [value]); + exec(null, null, 'SystemBarPlugin', 'setStatusBarBackgroundColor', rgbVals); } } }); diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index e4752a694..2926d1bbc 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -89,8 +89,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo boolean visible = args.getBoolean(0); cordova.getActivity().runOnUiThread(() -> setStatusBarVisible(visible)); } else if ("setStatusBarBackgroundColor".equals(action)) { - String bgColor = args.getString(0); - cordova.getActivity().runOnUiThread(() -> setStatusBarBackgroundColor(bgColor)); + cordova.getActivity().runOnUiThread(() -> setStatusBarBackgroundColor(args)); } else { return false; } @@ -120,17 +119,27 @@ private void setStatusBarVisible(final boolean visible) { /** * Allow the app to override the status bar background color from JS API. - * If the supplied string is invalid and fails to parse, it will silently ignore + * If the supplied ARGB is invalid or fails to parse, it will silently ignore * the change request. * - * @param colorPref hex string + * @param argbVals {A, R, G, B} */ - private void setStatusBarBackgroundColor(final String colorPref) { - int parsedColor = parseColorFromString(colorPref); - if (parsedColor == INVALID_COLOR) return; - - overrideStatusBarBackgroundColor = parsedColor; - updateStatusBar(overrideStatusBarBackgroundColor); + private void setStatusBarBackgroundColor(JSONArray argbVals) { + try { + int a = argbVals.getInt(0); + int r = argbVals.getInt(1); + int g = argbVals.getInt(2); + int b = argbVals.getInt(3); + String hexColor = String.format("#%02X%02X%02X%02X", a, r, g, b); + + int parsedColor = parseColorFromString(hexColor); + if (parsedColor == INVALID_COLOR) return; + + overrideStatusBarBackgroundColor = parsedColor; + updateStatusBar(overrideStatusBarBackgroundColor); + } catch (JSONException e) { + // Silently skip + } } /** From 85630c2c3adfbd36af7e5ef1bc684f7cdb1b1efd Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 9 Jul 2025 12:37:57 +0900 Subject: [PATCH 07/11] chore: suppress deprecation warnings for method using setNavigationBarColor --- framework/src/org/apache/cordova/SystemBarPlugin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index 2926d1bbc..fbdce35da 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -185,6 +185,7 @@ private void updateSystemBars() { * * @param bgColor Background color */ + @SuppressWarnings("deprecation") private void updateRootView(int bgColor) { Window window = cordova.getActivity().getWindow(); From 121f5a58b2d72e3b0182698e89f25747a087e471 Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 9 Jul 2025 12:57:10 +0900 Subject: [PATCH 08/11] chore: return null when rootView is null --- framework/src/org/apache/cordova/SystemBarPlugin.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index fbdce35da..a70b96a8d 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -306,7 +306,11 @@ private FrameLayout getRootLayout(CordovaWebView webView) { */ private View getStatusBarView(CordovaWebView webView) { FrameLayout rootView = getRootLayout(webView); - for (int i = 0; i < (rootView != null ? rootView.getChildCount() : 0); i++) { + if (rootView == null) { + return null; + } + + for (int i = 0; i < rootView.getChildCount(); i++) { View child = rootView.getChildAt(i); Object tag = child.getTag(); if ("statusBarView".equals(tag)) { From 66591a6bf700cc6ecf5505437ce3ea86629613b3 Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 9 Jul 2025 13:27:50 +0900 Subject: [PATCH 09/11] fix: setOnApplyWindowInsetsListener to return insets --- framework/src/org/apache/cordova/CordovaActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java index 463f52439..4cfabb647 100755 --- a/framework/src/org/apache/cordova/CordovaActivity.java +++ b/framework/src/org/apache/cordova/CordovaActivity.java @@ -239,7 +239,7 @@ protected void createViews() { ); statusBarView.setLayoutParams(statusBarParams); - return WindowInsetsCompat.CONSUMED; + return insets; }); rootLayout.addView(webView); From bd72c0654e50588bc0acb365bf6122a3a8a3bef8 Mon Sep 17 00:00:00 2001 From: Erisu Date: Wed, 9 Jul 2025 13:48:54 +0900 Subject: [PATCH 10/11] fix: setting appearance when e2e is enabled --- .../src/org/apache/cordova/SystemBarPlugin.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/framework/src/org/apache/cordova/SystemBarPlugin.java b/framework/src/org/apache/cordova/SystemBarPlugin.java index a70b96a8d..728dca8d1 100644 --- a/framework/src/org/apache/cordova/SystemBarPlugin.java +++ b/framework/src/org/apache/cordova/SystemBarPlugin.java @@ -194,7 +194,12 @@ private void updateRootView(int bgColor) { if (root != null) root.setBackgroundColor(bgColor); // Automatically set the font and icon color of the system bars based on background color. - boolean isBackgroundColorLight = isColorLight(bgColor); + boolean isBackgroundColorLight; + if(bgColor == Color.TRANSPARENT) { + isBackgroundColorLight = isColorLight(getUiModeColor()); + } else { + isBackgroundColorLight = isColorLight(bgColor); + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { WindowInsetsController controller = window.getInsetsController(); if (controller != null) { @@ -232,7 +237,12 @@ private void updateStatusBar(int bgColor) { } // Automatically set the font and icon color of the system bars based on background color. - boolean isStatusBarBackgroundColorLight = isColorLight(bgColor); + boolean isStatusBarBackgroundColorLight; + if(bgColor == Color.TRANSPARENT) { + isStatusBarBackgroundColorLight = isColorLight(getUiModeColor()); + } else { + isStatusBarBackgroundColorLight = isColorLight(bgColor); + } WindowInsetsControllerCompat controllerCompat = WindowCompat.getInsetsController(window, window.getDecorView()); controllerCompat.setAppearanceLightStatusBars(isStatusBarBackgroundColorLight); } From 11593679267e584a113a1497a042fa2d995cb141 Mon Sep 17 00:00:00 2001 From: Erisu Date: Thu, 11 Sep 2025 17:41:48 +0900 Subject: [PATCH 11/11] fix: set statusBarColor to transparent, use new statusBar UI --- templates/project/res/values/cdv_themes.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/project/res/values/cdv_themes.xml b/templates/project/res/values/cdv_themes.xml index 9a7588abe..a59b589f6 100644 --- a/templates/project/res/values/cdv_themes.xml +++ b/templates/project/res/values/cdv_themes.xml @@ -37,5 +37,6 @@