Search code examples
javaandroidqtqt6windowinsets

Android API 35+ Status/Navigation bar color/height from Java


So far I am using to set background color and get statusBar/navigationBar/cutout programaticaly from Java function, however its geting very old and most if its its deprecated in current Android API 35+.

So I googled as much as I could, used many exmaples to rerite to new functions, however the height of statusBar, navigationBar and cutout always returns zero, which is obvioulsy wrong.

In the following example I commented out by "OLD" how I use to use working code, by "NEW" which is not working new code, and by "NOTE" to is the open question I dont even know how to replace OLD by NEW.

package org.myapp.activity;

import org.qtproject.qt.android.QtNative;
import org.qtproject.qt.android.bindings.QtActivity;

import android.os.*;
import android.content.*;
import android.app.*;

import android.content.res.Resources;
import android.content.res.Configuration;
import android.util.DisplayMetrics;

import android.view.Display;

import android.hardware.display.DisplayManager;
import android.view.Surface;
import android.view.View;
import android.view.DisplayCutout;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.graphics.Color;

import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsCompat.Type.InsetsType;


public class MyActivity extends QtActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setCustomStatusAndNavigationBar();
    } // onCreate


    void setCustomStatusAndNavigationBar() {

        // First check sdk version, custom/transparent System_bars are only available after LOLLIPOP
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Window window = getWindow();

            // OLD: // The Window flag 'FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS' will allow us to paint the background of the status bar ourself and automatically expand the canvas
            // OLD: // If you want to simply set a custom background color (including transparent) for the statusBar/navigationBar, use the following addFlags call
            // OLD: window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            // NOTE: I found no mention if this is even needed when not using setStatusBarcolor() anymore, but will use WindowInsets.Type.statusBars() - my guess its not needed anymore

            // OLD: // The Window flag 'FLAG_TRANSLUCENT_NAVIGATION' will allow us to paint the background of the navigation bar ourself
            // OLD: // But we will also have to deal with orientation and OEM specifications, as the navigationBar may or may not depend on the orientation of the device
            // OLD: window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);     // DEPRECATED (NOTE: is this even needed as setStatusBarColor() and setNavigationBarColor() are deprecated?)

            // OLD: window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);                 // DEPRECATED
            // NOTE: Use WindowInsetsController instead (NOTE: is this even needed as setStatusBarColor() and setNavigationBarColor() are deprecated?)

            // OLD: Set StatusBar Transparent
            // OLD: window.setStatusBarColor(Color.TRANSPARENT);            // DEPRECATED
            // NOTE: Draw proper background behind WindowInsets.Type#statusBars()} instead

            // OLD: // Set NavigationBar to desired color (0xAARRGGBB) set alpha value to 0 if you want a solid color
            // OLD: window.setNavigationBarColor(Color.TRANSPARENT);        // DEPRECATED
            // NOTE: Draw proper background behind WindowInsets.Type#navigationBars() instead


            // OLD: // Statusbar background is now transparent, but the icons and text are probably white and not really readable, as we have a bright background color
            // OLD: // We set/force a light theme for the status bar to make those dark
            // OLD: View decor = window.getDecorView();
            // OLD: decor.setSystemUiVisibility(decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);          // DEPRECATED
            // NOTE: Use WindowInsetsController#APPEARANCE_LIGHT_NAVIGATION_BARS instead. I guess something like:
            window.insetsController?.setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);     // for light mode, therefore dark foreground (text/icons)... NOTE: does it works on its own or do I need to set something specific, like addFlags() to make this work?
            //window.insetsController?.setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);     // for dark mode, therefore light foreground (text/icons)... NOTE: does it works on its own or do I need to set something specific, like addFlags() to make this work
            
            // NOTE: Use WindowInsetsController#APPEARANCE_LIGHT_STATUS_BARS instead. I guess something like:
            window.insetsController?.setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS);     // for light mode, therefore dark foreground (text/icons)... NOTE: does it works on its own or do I need to set something specific, like addFlags() to make this work?
            //window.insetsController?.setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS);     // for dark mode, therefore light foreground (text/icons)... NOTE: does it works on its own or do I need to set something specific, like addFlags() to make this work

        }
    }

    
    // NOTE: used info from: outdated https://medium.com/javarevisited/how-to-get-status-bar-height-in-android-programmatically-c127ad4f8a5d / unfortunately not link to github so I cant use whole example inluding includes, which I assume is my main problem
    public double statusBarHeight() {

        // OLD: Using Resources (still working, but preferable use current API solution)
        // double result = 0;
        // int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        // if (resourceId > 0) {
        //    result = getResources().getDimension(resourceId);
        // }
        // return result;


        // NEW: Using Window Insets (API 30+)
        WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
        double statusBarHeight = windowInsets.getInsets(WindowInsets.Type.statusBars()).top;    // returns 0 all the time, why?
    }

    public int safeAreaTop() {

        // OLD
        // DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
        // if(cutout != null) {
        //     int cutoutHeight = cutout.getSafeInsetTop();
        //     if (cutoutHeight > 0) {
        //         return cutoutHeight;
        //      }
        // }
        // return 0;

        // NEW: Using Window Insets (API 30+)
        WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
        int cutoutHeight = windowInsets.getInsets(WindowInsets.Type.displayCutout()).top;   // returns 0 all the time, but not sure if correctly as I use only Android Simualtor, but if statusBarHeight() solution is not working, I assume also this does not work
        return cutoutHeight;
    }

    public double navigationBarHeight() {
    
        // OLD: Using Resources (still working, but preferable use current API solution)
        // double result = 0;
        // int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        // if (resourceId > 0) {
        //    result = getResources().getDimension(resourceId);
        // }
        // return result;


        // New: Using Window Insets (API 30+)
        WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
        double navigationBarHeight = windowInsets.getInsets(WindowInsets.Type.navigationBars()).bottom;     // returns 0 all the time, but not sure if correctly as I use only Android Simualtor without navigationBar, but if statusBarHeight() solution is not working, I assume also this does not work
        return navigationBarHeight;
    }

    public int getNavigationBarPosition() {

        Resources res = getResources();
        int resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android");
        boolean hasMenu = false;
        if (resourceId > 0) {
            hasMenu =  res.getBoolean(resourceId);
        }
        if (!hasMenu) {
            return -1;
        }


        // NOTE: https://stackoverflow.com/questions/69724946/getdisplay-return-display
        // Display display = getWindowManager().getDefaultDisplay();        // OLD: getDefaultDisplay() DEPRECATED
        DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);     // NEW solution, currently untested
        Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
        int rotation = display.getRotation();

        switch (rotation) {
            case Surface.ROTATION_90:
                return 1;
            case Surface.ROTATION_180:
                return 3;
            case Surface.ROTATION_270:
                return 2;
            default:
                return 0;
        }
    }
}

can I kindly ask for a check and explanation what did I do wrong in here along with the correction how the proper working code shall looks like?

PS: I am not an Java expert, I work in QT (so C++/QML), this is only thing needed for my app to manage from Java interface


Solution

  • The new Android 15 behavior spells out the kinds of things you'll need to do. https://developer.android.com/about/versions/15/behavior-changes-15#ux

    Not sure where things like safeAreaTop are called and used, but you should be getting Insets as they apply to a particular view (binding.homeFrame in this example) and updating the padding or margins for that view as needed

    ViewCompat.setOnApplyWindowInsetsListener(binding.homeFrame) { v, windowInsets ->
            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
            v.updatePadding(insets.left, 0, insets.right, insets.bottom)
            WindowInsetsCompat.CONSUMED
        }