Detect soft navigation bar availability in onePlusOne device progmatically? - android

I need to check if a device has the soft navigation bar, and I followed the suggestions here.
It works great, except on onePlus devices, for some reason, this code:
int id = resources.getIdentifier("config_showNavigationBar", "bool", android");
return id > 0 && resources.getBoolean(id);
returns false, although the soft navigation bar is displayed.
Any idea how can I get the correct result?
I prefer not to calculate the real width and available width, it seems like expensive operation.
Thanks.

See this answer. There is no way be 100% sure, though.
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);
if (hasBackKey && hasHomeKey) {
// no navigation bar, unless it is enabled in the settings
} else {
// 99% sure there's a navigation bar
}
Edit
Another approach
public boolean hasNavBar (Resources resources) {
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
return id > 0 && resources.getBoolean(id);
}

Yes you can try this:
WindowManager mgr = (WindowManager) getSystemService(WINDOW_SERVICE);
boolean hasSoftKey = Utils.hasSoftKeys(mgr, NPTApplication.this);
public static boolean hasSoftKeys(WindowManager windowManager, Context c) {
boolean hasSoftwareKeys = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Display d = windowManager.getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
hasSoftwareKeys = (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
} else {
boolean hasMenuKey = ViewConfiguration.get(c).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
hasSoftwareKeys = !hasMenuKey && !hasBackKey;
}
return hasSoftwareKeys;
}

no will not work that way you have to compute the size
the method used is detailed in this SO answer;
How to get height and width of navigation bar programmatically

Well, there is a method hasPermanentMenuKey which checks if the hardware MenuKey is present, usually Samsung devices has it on left size of Home button.
So if it returns true, it means the phone has hardware keys, and if it's false, then simply it means phone has navigation bar.
The method is:
ViewConfiguration.hasPermanentMenuKey()
I find it pretty useful for myself.

//The method has ability to return 0 when the navigation bar was hidden.
private fun getNavigationBarHeight(context: Context): Int {
val display = context.windowManager?.defaultDisplay
return if (display == null) {
0
} else {
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
val metrics = DisplayMetrics()
display.getMetrics(metrics)
realMetrics.heightPixels - metrics.heightPixels
}
}

Related

How to find dimensions for status bar, navigation bar and screen in Android AccessibilityService

I have a Java app (API 23). In it I have a MainActivity and an AccessibilityService. In the AccessibilityService:
#Override
public void onServiceConnected() {
oView = new LinearLayout(this);
oView.setBackgroundColor(0x88ff0000); // The translucent red color
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
width,
height,
xPos, yPos,
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT
);
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
wm.addView(oView, params);
}
I need to adjust the width, height, xPos and yPos so it covers the whole screen including the status bar and navigation bar. That means I have to find the dimensions for the status bar, screen, and navigation bar (I can work out the values I need once I have those).
I found Height of statusbar? - best answer seemed to be
ViewCompat.setOnApplyWindowInsetsListener(rootView, new OnApplyWindowInsetsListener() {
#Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
//THIS is the value you want.
int statusBarHeight = insets.getSystemWindowInsetTop();
int navigationBar = insets.getSystemWindowInsetBottom();
// Let the view handle insets as it likes.
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
But what rootView can I use, since there's no guarantee my Activity will be visible? And do I have to move the creation of the view from onServiceConnected() to inside onApplyWindowInsets()?
Is this really the best way to do this?
Follow up questions to bear in mind for the above - depending on the above and whether it answers them too, I will ask here or in a separate question:
What happens when the screen rotates? What if the status bar and/or navigation bar disappear (for example, if it goes fullscreen)? Seems like I need to handle that, re-read the heights, and re-create the view. Any thoughts or guidance on how to do that would be welcomed.
=== Update - Progress so far ===
I thought I only needed screen size plus info about the status bar (size, location), but Navigation Bar is sometimes on the left side of the screen so I need that, and Keyboard might affect things too.
I can get screen size with
Display myDisplay = wm.getDefaultDisplay();
Display.Mode mode = myDisplay.getMode();
int height = mode.getPhysicalHeight();
int width = mode.getPhysicalWidth();
I'm still working on the various location and sizes. I tried creating a view and then reading insets but it gave me the wrong answers. So unless a better answer arises I will probably have to use
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
But that can give wrong answers in some cases if Status bar is hidden, and it doesn't tell me where on the screen it is. I'm still working on that; got a few things to try, so we'll see.
Handling rotation in a service can be done using onConfigurationChanged() - see How do I use a service to monitor Orientation change in Android
Hope that helps someone - please, desperately searching for those final pieces.
Update: There's a problem when the screen is rotated upside-down. See Find placement of Android Navigation Bar without Insets (inside Service)
I'm currently working on a solution with my own research combined with https://stackoverflow.com/a/55348825/1910690
But I really wish I could just use Insets, or something like that.
==============
I tried a bunch of things that seemed to fail because I didn't have an activity; it's possible if I had poked around some more I would have made it work with Insets, and I still hope for an answer using them as they are more official and allow for notches etc.
In the meantime I finally pieced various answers together to make the following. It would probably work for a normal Service as well as an AccessibilityService.
The point is that at the end of onGlobalLayout() inside CreateLayoutHelperWnd() you have the complete information on screen size, orientation, status bar and navigation bar visibility and size, and you know that either you've just started your service or something changed. I found that it gets called twice sometimes even though nothing changed, probably because sometimes it does 'hide status+nav bar' and then 'change orientation' (same results for both), and sometimes it does it the other way around (different results) so I have wrapped the results in a class, and I compare the new results with the results from the last time and only do something if a result changed (that's why I use so many global variables, because those got moved later to the special class).
Please note that this is my first serious foray into Android and Java, so apologies if it's badly done - comments welcomed. It also doesn't handle error checking (eg StatusBar not found). Also note that I have not tested this extensively across multiple devices, I have no idea what it does with folding devices, multiple displays, or even split-screen; but I tried to account for the issues I saw in the various solutions (eg reading Status Bar height sometimes gives the wrong answer when it's closed on some devices, so I try to check if it is open or closed). I'll update if needed as I test more.
In the AccessibilityService:
// global variables
int statusBarHeight = -1;
int navigationBarHeight = -1;
int screenWidth = -1;
int screenHeight = -1;
View layoutHelperWnd;
#Override
public void onServiceConnected() {
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
CreateLayoutHelperWnd(wm);
}
public void onUnbind() {
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
DestroyLayoutHelperWnd(wm);
}
private void DestroyLayoutHelperWnd(WindowManager wm) {
wm.removeView(layoutHelperWnd);
}
private void CreateLayoutHelperWnd(WindowManager wm) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
p.type = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY :
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
p.gravity = Gravity.RIGHT | Gravity.TOP;
p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
p.width = WindowManager.LayoutParams.MATCH_PARENT;
p.height = WindowManager.LayoutParams.MATCH_PARENT;
p.format = PixelFormat.TRANSPARENT;
layoutHelperWnd = new View(this); //View helperWnd;
wm.addView(layoutHelperWnd, p);
final ViewTreeObserver vto = layoutHelperWnd.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// catches orientation change and fullscreen/not fullscreen change
// read basic screen setup - not sure if needed every time, but can't hurt
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
ReadScreenDimensions(wm); // sets up screenWidth and screenHeight
statusBarHeight = GetStatusBarHeight();
navigationBarHeight = GetNavigationBarHeight();
int windowHeight = layoutHelperWnd.getHeight();
int windowWidth = layoutHelperWnd.getWidth();
Boolean isFullScreen, isStatusBar, isNavigationBar;
isFullScreen = isStatusBar = isNavigationBar = false;
Configuration config = getResources().getConfiguration();
if (config.orientation == ORIENTATION_LANDSCAPE) {
// landscape - screenWidth is for comparison to windowHeight (top to bottom), status bar may be on top, nav bar may be on right
if (screenWidth != windowHeight)
isStatusBar = true;
if (screenHeight != windowWidth)
isNavigationBar = true;
}
else {
// portrait, square, unknown - screenHeight is for comparison to windowHeight (top to bottom), status bar may be on top, nav bar may be on bottom
if (screenHeight != windowHeight) {
int difference = screenHeight - windowHeight;
if (difference == statusBarHeight)
isStatusBar = true;
else if (difference == navigationBarHeight)
isNavigationBar = true;
else {
isStatusBar = true;
isNavigationBar = true;
}
}
}
if (!isStatusBar && !isNavigationBar)
isFullScreen = true;
Log.v(TAG,"Screen change:\nScreen W,H: (" + screenWidth + ", " + screenHeight + ")\nOrientation: " + (config.orientation == ORIENTATION_LANDSCAPE ? "Landscape" : config.orientation == ORIENTATION_PORTRAIT ? "Portrait" : "Unknown") +
"\nWindow W,H: (" + windowWidth + ", " + windowHeight + ")\n" + "Status bar H: " + statusBarHeight + ", present: " + isStatusBar + "\nNavigation bar H: " + navigationBarHeight + ", present: " + isNavigationBar + "\nFullScreen: " + isFullScreen);
// do any updates required to the service's assets here
}
});
}
public void ReadScreenDimensions(WindowManager wm) {
Display myDisplay = wm.getDefaultDisplay();
Display.Mode mode = myDisplay.getMode();
screenHeight = mode.getPhysicalHeight();
screenWidth = mode.getPhysicalWidth();
}
public int GetStatusBarHeight() {
// returns 0 for no result found
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public int GetNavigationBarHeight() {
// returns 0 for no result found
int result = 0;
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return getResources().getDimensionPixelSize(resourceId);
}
return result;
}
You will also need to add to your manifest (for the app, not the service):
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
and in your main activity call isSystemAlertPermissionGranted() and then do something meaningful depending on what is returned (possibly waiting until onActivityResult() in some cases). Er, I basically grabbed this wholesale from various places and have not changed it much or at all. :)
public static int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE= 1234;
public boolean isSystemAlertPermissionGranted() {
// if this doesn't work, try https://stackoverflow.com/questions/46208897/android-permission-denied-for-window-type-2038-using-type-application-overlay
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
return false;
}
else
{
Log.v(TAG,"Permission is granted");
return true;
}
}
else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG, "Permission is granted");
return true;
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
// Check if the app get the permission
if (Settings.canDrawOverlays(this)) {
// Run your logic with newly-granted permission.
} else {
// Permission not granted. Change your logic accordingly.
// App can re-request permission anytime.
}
}
}

How to REALLY get the navigation bar height in Android

I own a HTC One A9 which has the ability to hide the navigation bar. In my app, I need to get the height of the navigation bar and set a padding corresponding to it. Here's my problem now: When I hide the navigation bar, the padding is still being set (even Snapchat has this problem). My question is: Is there alternative code to this one that makes it work?
public static int getNavBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
Thanks for your help!
This is the code I use to get the navigation bar size. Its height will be in Point.y
Credit to this answer
public static Point getNavigationBarSize(Context context) {
Point appUsableSize = getAppUsableScreenSize(context);
Point realScreenSize = getRealScreenSize(context);
// navigation bar on the right
if (appUsableSize.x < realScreenSize.x) {
return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
}
// navigation bar at the bottom
if (appUsableSize.y < realScreenSize.y) {
return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
}
// navigation bar is not present
return new Point();
}
public static Point getAppUsableScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}
public static Point getRealScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
if (Build.VERSION.SDK_INT >= 17) {
display.getRealSize(size);
} else if (Build.VERSION.SDK_INT >= 14) {
try {
size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
} catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {}
}
return size;
}
Edit: To answer your question I had to use this function since I wanted to add ResideMenu to my app, but ended getting a weird empty margin at the bottom of my app, because of the navigation bar.
So I edited this function added by ResideMenu like this:
#Override
protected boolean fitSystemWindows(Rect insets) {
// Applies the content insets to the view's padding, consuming that content (modifying the insets to be 0),
// and returning true. This behavior is off by default and can be enabled through setFitsSystemWindows(boolean)
// in API14+ devices.
int bottomPadding = insets.bottom;
Point p = getNavigationBarSize(getContext());
if (Build.VERSION.SDK_INT >= 21 && p.x != 0) {
Resources resources = getContext().getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
bottomPadding += resources.getDimensionPixelSize(resourceId);
}
}
this.setPadding(viewActivity.getPaddingLeft() + insets.left, viewActivity.getPaddingTop() + insets.top,
viewActivity.getPaddingRight() + insets.right, viewActivity.getPaddingBottom() + bottomPadding);
insets.left = insets.top = insets.right = insets.bottom = 0;
return true;
}
Hope that will help you.
Use following code to get Navigation bar. However, you need to consider Multi window mode as well as whether Navigation bar is at the bottom or on left side or right side of the window.
getNavigationBarHeight(){
Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
return 0;
}
Here is the simplest answer. You just need to pass the rootView of the activity. The heights will be 0 if the the bars are not shown.
View rootView;
ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
final int statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top; // in px
final int navigationBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; // in px
// do something with the heights
return WindowInsetsCompat.CONSUMED;
});
fun Context.isSoftNavigationBarAvailable(): Boolean {
val navBarInteractionModeId = resources.getIdentifier(
"config_navBarInteractionMode",
"integer",
"android"
)
if (navBarInteractionModeId > 0 && resources.getInteger(navBarInteractionModeId) > 0) {
// nav gesture is enabled in the settings
return false
}
val appUsableScreenSize = Point()
val realScreenSize = Point()
val defaultDisplay = (getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
defaultDisplay.getSize(appUsableScreenSize)
defaultDisplay.getRealSize(realScreenSize)
return appUsableScreenSize.y < realScreenSize.y }
From Android R (SDK 30+), you can use this code to get size of status bar and navigation bar
WindowInsets insets = activity.getWindowManager().getCurrentWindowMetrics().getWindowInsets();
int statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top; //in pixels
int navigationBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; //in pixels

How to detect bottom soft navigation bar available in android programmatically?

I am trying to determine soft navigation bar through the android program. I didn't find straight way to determine. Is there anyway to find the navigation bar availability.
Soft Navigation bar image is here.
Following method worked for me and tested in many devices.
public boolean hasNavBar (Resources resources)
{
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
return id > 0 && resources.getBoolean(id);
}
Note: Verified this method in real device
As i know you can detect it by
boolean hasSoftKey = ViewConfiguration.get(context).hasPermanentMenuKey();
But it required APIs 14+
If above solution doesn't work for you then try below method
public boolean isNavigationBarAvailable(){
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);
return (!(hasBackKey && hasHomeKey));
}
Its a hack but it works fine. Try it.
public static boolean hasSoftKeys(WindowManager windowManager){
Display d = windowManager.getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
}
The accepted answer should work fine on most real devices, but it doesn't work in the emulators.
However, in Android 4.0 and above, there's an internal API that also works on the emulators: IWindowManager.hasNavigationBar(). You can access it using reflection:
/**
* Returns {#code null} if this couldn't be determined.
*/
#TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
#SuppressLint("PrivateApi")
public static Boolean hasNavigationBar() {
try {
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
IBinder serviceBinder = (IBinder)serviceManager.getMethod("getService", String.class).invoke(serviceManager, "window");
Class<?> stub = Class.forName("android.view.IWindowManager$Stub");
Object windowManagerService = stub.getMethod("asInterface", IBinder.class).invoke(stub, serviceBinder);
Method hasNavigationBar = windowManagerService.getClass().getMethod("hasNavigationBar");
return (boolean)hasNavigationBar.invoke(windowManagerService);
} catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
Log.w("YOUR_TAG_HERE", "Couldn't determine whether the device has a navigation bar", e);
return null;
}
}
Try this method,in this way you can detect if the navigation bar exist.
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public boolean hasNavBar(Context context) {
Point realSize = new Point();
Point screenSize = new Point();
boolean hasNavBar = false;
DisplayMetrics metrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
realSize.x = metrics.widthPixels;
realSize.y = metrics.heightPixels;
getWindowManager().getDefaultDisplay().getSize(screenSize);
if (realSize.y != screenSize.y) {
int difference = realSize.y - screenSize.y;
int navBarHeight = 0;
Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navBarHeight = resources.getDimensionPixelSize(resourceId);
}
if (navBarHeight != 0) {
if (difference == navBarHeight) {
hasNavBar = true;
}
}
}
return hasNavBar;
}
Right answer and other are not actual now.
There are exist some options like 'Full Screen Display -> Full Screen Gestures' where navigation bar is hidden but all this methods returns that he is present.
I suggest you to use this way to check size of system views.
In onCreate method:
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content),
(v, insets) -> {
int navigationBarHeight = insets.getSystemWindowInsetBottom();
return insets;
});
Other answers don't help me. But it's quite useful to know if navigation bar is shown, especially after Android P/Q, where user can swipe it out of screen. I've encounter this article https://blog.stylingandroid.com/gesture-navigation-window-insets/ and made such method
fun hasNavBar(activity: Activity): Boolean {
val temporaryHidden = activity.window.decorView.visibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION != 0
if (temporaryHidden) return false
val decorView = activity.window.decorView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
decorView.rootWindowInsets?.let{
return it.stableInsetBottom != 0
}
}
return true
}
The point of finding the soft navigation enabled or not is to determine what is the size of the window provided and to set the layout according to.
There is a very powerful tool called decorView that sets the window from before so as to directly implement methods under it. It can be written like this:
val decorView = window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
Just start writing methods under it whichever you want to make inside the frame.
Found this one working after trying a ton of methods and tricks and this one is the only one working.

Check if Android device has software or hardware navigation buttons

I'm writing an Android app with a transparent navigation bar. To adjust the layout accordingly, I need to know whether the device has software or hardware buttons.
During my research I came across the following solutions:
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
This approach does not work on some new devices like the Samsung Note 4 or the HTC One which do not have a menu key, but still have hardware buttons.
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
boolean hasHomeKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME);
boolean hasHardwareButtons = hasBackKey && hasHomeKey;
This does not work as well, because some devices like the Sony Xperia Z3 Compact return true although it has a software navigation bar.
Is there any reliable way to find out whether there are physical buttons or a software navigation bar?
See this answer. You can get the real height of the display in pixels, and then get the height available to your app, and figure out if the device has an on-screen navigation bar with these values.
The answer posted here did the trick. Just for completeness, here is the code I am using now:
private static boolean hasImmersive;
private static boolean cached = false;
#SuppressLint ("NewApi")
public static boolean hasImmersive(Context ctx) {
if (!cached) {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
hasImmersive = false;
cached = true;
return false;
}
Display d = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
d.getRealMetrics(realDisplayMetrics);
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
hasImmersive = (realWidth > displayWidth) || (realHeight > displayHeight);
cached = true;
}
return hasImmersive;
}

How do I get the height and width of the Android Navigation Bar programmatically?

The black navigation bar on the bottom of the screen is not easily removable in Android. It has been part of Android since 3.0 as a replacement for hardware buttons. Here is a picture:
How can I get the size of the width and the height of this UI element in pixels?
Try below code:
Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
return 0;
I get navigation bar size by comparing app-usable screen size with real screen size. I assume that navigation bar is present when app-usable screen size is smaller than real screen size. Then I calculate navigation bar size. This method works with API 14 and up.
public static Point getNavigationBarSize(Context context) {
Point appUsableSize = getAppUsableScreenSize(context);
Point realScreenSize = getRealScreenSize(context);
// navigation bar on the side
if (appUsableSize.x < realScreenSize.x) {
return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
}
// navigation bar at the bottom
if (appUsableSize.y < realScreenSize.y) {
return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
}
// navigation bar is not present
return new Point();
}
public static Point getAppUsableScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}
public static Point getRealScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
if (Build.VERSION.SDK_INT >= 17) {
display.getRealSize(size);
} else if (Build.VERSION.SDK_INT >= 14) {
try {
size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
} catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {}
}
return size;
}
UPDATE
For a solution that takes into account display cutouts please check John's answer.
The NavigationBar height varies for some devices, but as well for some orientations. First you have to check if the device has a navbar, then if the device is a tablet or a not-tablet (phone) and finally you have to look at the orientation of the device in order to get the correct height.
public int getNavBarHeight(Context c) {
int result = 0;
boolean hasMenuKey = ViewConfiguration.get(c).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
if(!hasMenuKey && !hasBackKey) {
//The device has a navigation bar
Resources resources = c.getResources();
int orientation = resources.getConfiguration().orientation;
int resourceId;
if (isTablet(c)){
resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android");
} else {
resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android");
}
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
}
return result;
}
private boolean isTablet(Context c) {
return (c.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
Actually the navigation bar on tablets (at least Nexus 7) has different size in portrait and landscape so this function should look like this:
private int getNavigationBarHeight(Context context, int orientation) {
Resources resources = context.getResources();
int id = resources.getIdentifier(
orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape",
"dimen", "android");
if (id > 0) {
return resources.getDimensionPixelSize(id);
}
return 0;
}
and in Kotlin:
private fun getNavigationBarHeight(): Int {
val resources: Resources = requireContext().resources
val resName = if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
"navigation_bar_height"
} else {
"navigation_bar_height_landscape"
}
val id: Int = resources.getIdentifier(resName, "dimen", "android")
return if (id > 0) {
resources.getDimensionPixelSize(id)
} else {
0
}
}
I think better answer is here because it allows you to get even cutout height too.
Take your root view, and add setOnApplyWindowInsetsListener (or you can override onApplyWindowInsets from it), and take insets from it.
In my camera activity, i add padding equal to the systemBars.bottom to my bottom layout. And finally, it fix cutout issue.
with appcompat it is like this
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
binding.takePictureLayout.apply {
setPaddingRelative(paddingStart, paddingTop, paddingEnd, systemBars.bottom)
}
return#setOnApplyWindowInsetsListener insets
}
without appcompat, this:
mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })
I hope this helps you
public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public int getNavigationBarHeight()
{
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0 && !hasMenuKey)
{
return getResources().getDimensionPixelSize(resourceId);
}
return 0;
}
New answer in 2021 comes to the rescue
insipred from Egis's answer:
context.navigationBarHeight
where the extension getter is
val Context.navigationBarHeight: Int
get() {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
return if (Build.VERSION.SDK_INT >= 30) {
windowManager
.currentWindowMetrics
.windowInsets
.getInsets(WindowInsets.Type.navigationBars())
.bottom
} else {
val currentDisplay = try {
display
} catch (e: NoSuchMethodError) {
windowManager.defaultDisplay
}
val appUsableSize = Point()
val realScreenSize = Point()
currentDisplay?.apply {
getSize(appUsableSize)
getRealSize(realScreenSize)
}
// navigation bar on the side
if (appUsableSize.x < realScreenSize.x) {
return realScreenSize.x - appUsableSize.x
}
// navigation bar at the bottom
return if (appUsableSize.y < realScreenSize.y) {
realScreenSize.y - appUsableSize.y
} else 0
}
}
tested on:
emulators with navigation bars
pixel 3a (api 30)
pixel 2 (api 28)
pixel 3 (api 25)
pixel 2 (api 21)
Xiaomi Poco f2 pro with & without navigation bar(full display)
This is my code to add paddingRight and paddingBottom to a View to dodge the Navigation Bar. I combined some of the answers here and made a special clause for landscape orientation together with isInMultiWindowMode. The key is to read navigation_bar_height, but also check config_showNavigationBar to make sure we should actually use the height.
None of the previous solutions worked for me. As of Android 7.0 you have to take Multi Window Mode into consideration. This breaks the implementations comparing display.realSize with display.size since realSize gives you the dimensions of the whole screen (both split windows) and size only gives you the dimensions of your App window. Setting padding to this difference will leave your whole view being padding.
/** Adds padding to a view to dodge the navigation bar.
Unfortunately something like this needs to be done since there
are no attr or dimens value available to get the navigation bar
height (as of December 2016). */
public static void addNavigationBarPadding(Activity context, View v) {
Resources resources = context.getResources();
if (hasNavigationBar(resources)) {
int orientation = resources.getConfiguration().orientation;
int size = getNavigationBarSize(resources);
switch (orientation) {
case Configuration.ORIENTATION_LANDSCAPE:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
context.isInMultiWindowMode()) { break; }
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
v.getPaddingRight() + size, v.getPaddingBottom());
break;
case Configuration.ORIENTATION_PORTRAIT:
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
v.getPaddingRight(), v.getPaddingBottom() + size);
break;
}
}
}
private static int getNavigationBarSize(Resources resources) {
int resourceId = resources.getIdentifier("navigation_bar_height",
"dimen", "android");
return resourceId > 0 ? resources.getDimensionPixelSize(resourceId) : 0;
}
private static boolean hasNavigationBar(Resources resources) {
int hasNavBarId = resources.getIdentifier("config_showNavigationBar",
"bool", "android");
return hasNavBarId > 0 && resources.getBoolean(hasNavBarId);
}
The solution proposed by Egidijus and works perfectly for Build.VERSION.SDK_INT >= 17
But I got "NoSuchMethodException" during execution of the following statement with Build.VERSION.SDK_INT < 17 on my device:
Display.class.getMethod("getRawHeight").invoke(display);
I have modified the method getRealScreenSize() for such cases:
else if(Build.VERSION.SDK_INT >= 14)
{
View decorView = getActivity().getWindow().getDecorView();
size.x = decorView.getWidth();
size.y = decorView.getHeight();
}
I resolved this issue for all devices(including Nexus 5, Samsung Galaxy Nexus 6 edge+, Samsung S10, Samsung Note II etc.). I think this will help you to handle device dependant issues.
Here I am adding two types of codes,
Java Code(for Native Android):
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.ViewConfiguration;
import android.view.WindowManager;
public class DeviceSpec {
private int resourceID = -1;
private Display display = null;
private DisplayMetrics displayMetrics = null;
private DisplayMetrics realDisplayMetrics = null;
private Resources resources = null;
private WindowManager windowManager = null;
public double GetNavigationBarHeight(Context context) {
try {
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
display = windowManager.getDefaultDisplay();
displayMetrics = new DisplayMetrics();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
realDisplayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
display.getRealMetrics(realDisplayMetrics);
if(displayMetrics.heightPixels != realDisplayMetrics.heightPixels) {
resources = context.getResources();
return GetNavigationBarSize(context);
}
}
else {
resources = context.getResources();
resourceID = resources.getIdentifier("config_showNavigationBar", "bool", "android");
if (resourceID > 0 && resources.getBoolean(resourceID))
return GetNavigationBarSize(context);
}
}
catch (Exception e){
e.printStackTrace();
}
return 0;
}
private double GetNavigationBarSize(Context context) {
resourceID = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceID > 0 && ViewConfiguration.get(context).hasPermanentMenuKey())
return (resources.getDimensionPixelSize(resourceID) / displayMetrics.density);
return 0;
}
}
And C# code(for Xamarin Forms/Android)
int resourceId = -1;
IWindowManager windowManager = null;
Display defaultDisplay = null;
DisplayMetrics displayMatrics = null;
DisplayMetrics realMatrics = null;
Resources resources = null;
public double NavigationBarHeight
{
get
{
try
{
windowManager = Forms.Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
defaultDisplay = windowManager.DefaultDisplay;
displayMatrics = new DisplayMetrics();
if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2)
{
realMatrics = new DisplayMetrics();
defaultDisplay.GetMetrics(displayMatrics);
defaultDisplay.GetRealMetrics(realMatrics);
if (displayMatrics.HeightPixels != realMatrics.HeightPixels)
{
resources = Forms.Context.Resources;
return GetHeightOfNivigationBar();
}
}
else {
resources = Forms.Context.Resources;
resourceId = resources.GetIdentifier("config_showNavigationBar", "bool", "android");
if (resourceId > 0 && resources.GetBoolean(resourceId))
return GetHeightOfNivigationBar();
}
}
catch (Exception e) { }
return 0;
}
}
private double GetHeightOfNivigationBar()
{
resourceId = resources.GetIdentifier("navigation_bar_height", "dimen", "android");
if (!ViewConfiguration.Get(Forms.Context).HasPermanentMenuKey && resourceId > 0)
{
return resources.GetDimensionPixelSize(resourceId) / displayMatrics.Density;
}
return 0;
}
Tested code for getting height of navigation bar (in pixels):
public static int getNavBarHeight(Context c) {
int resourceId = c.getResources()
.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return c.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}
Tested code for getting height of status bar (in pixels):
public static int getStatusBarHeight(Context c) {
int resourceId = c.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
return c.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}
Converting pixels to dp:
public static int pxToDp(int px) {
return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}
How to get the height of the navigation bar and status bar. This code works for me on some Huawei devices and Samsung devices.
Egis's solution above is good, however, it is still incorrect on some devices. So, I improved it.
This is code to get the height of status bar
private fun getStatusBarHeight(resources: Resources): Int {
var result = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId)
}
return result
}
This method always returns the height of navigation bar even when the navigation bar is hidden.
private fun getNavigationBarHeight(resources: Resources): Int {
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}
NOTE: on Samsung A70, this method returns the height of the status bar + height of the navigation bar.
On other devices (Huawei), it only returns the height of the Navigation bar and returns 0 when the navigation bar is hidden.
private fun getNavigationBarHeight(): Int {
val display = activity?.windowManager?.defaultDisplay
return if (display == null) {
0
} else {
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
val metrics = DisplayMetrics()
display.getMetrics(metrics)
realMetrics.heightPixels - metrics.heightPixels
}
}
This is code to get height of navigation bar and status bar
val metrics = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getRealMetrics(metrics)
//resources is got from activity
//NOTE: on SamSung A70, this height = height of status bar + height of Navigation bar
//On other devices (Huawei), this height = height of Navigation bar
val navigationBarHeightOrNavigationBarPlusStatusBarHeight = getNavigationBarHeight()
val statusBarHeight = getStatusBarHeight(resources)
//The method will always return the height of navigation bar even when the navigation bar was hidden.
val realNavigationBarHeight = getNavigationBarHeight(resources)
val realHeightOfStatusBarAndNavigationBar =
if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == 0 || navigationBarHeightOrNavigationBarPlusStatusBarHeight < statusBarHeight) {
//Huawei: navigation bar is hidden
statusBarHeight
} else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == realNavigationBarHeight) {
//Huawei: navigation bar is visible
statusBarHeight + realNavigationBarHeight
} else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight < realNavigationBarHeight) {
//SamSung A70: navigation bar is still visible but it only displays as a under line
//navigationBarHeightOrNavigationBarPlusStatusBarHeight = navigationBarHeight'(under line) + statusBarHeight
navigationBarHeightOrNavigationBarPlusStatusBarHeight
} else {
//SamSung A70: navigation bar is visible
//navigationBarHeightOrNavigationBarPlusStatusBarHeight == statusBarHeight + realNavigationBarHeight
navigationBarHeightOrNavigationBarPlusStatusBarHeight
}
I've done this, it works on every device I tested, and even on emulators:
// Return the NavigationBar height in pixels if it is present, otherwise return 0
public static int getNavigationBarHeight(Activity activity) {
Rect rectangle = new Rect();
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rectangle);
activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
return displayMetrics.heightPixels - (rectangle.top + rectangle.height());
}
Combining the answer from #egis and others - this works well on a variety of devices, tested on Pixel EMU, Samsung S6, Sony Z3, Nexus 4. This code uses the display dimensions to test for availability of nav bar and then uses the actual system nav bar size if present.
/**
* Calculates the system navigation bar size.
*/
public final class NavigationBarSize {
private final int systemNavBarHeight;
#NonNull
private final Point navBarSize;
public NavigationBarSize(#NonNull Context context) {
Resources resources = context.getResources();
int displayOrientation = resources.getConfiguration().orientation;
final String name;
switch (displayOrientation) {
case Configuration.ORIENTATION_PORTRAIT:
name = "navigation_bar_height";
break;
default:
name = "navigation_bar_height_landscape";
}
int id = resources.getIdentifier(name, "dimen", "android");
systemNavBarHeight = id > 0 ? resources.getDimensionPixelSize(id) : 0;
navBarSize = getNavigationBarSize(context);
}
public void adjustBottomPadding(#NonNull View view, #DimenRes int defaultHeight) {
int height = 0;
if (navBarSize.y > 0) {
// the device has a nav bar, get the correct size from the system
height = systemNavBarHeight;
}
if (height == 0) {
// fallback to default
height = view.getContext().getResources().getDimensionPixelSize(defaultHeight);
}
view.setPadding(0, 0, 0, height);
}
#NonNull
private static Point getNavigationBarSize(#NonNull Context context) {
Point appUsableSize = new Point();
Point realScreenSize = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
Display display = windowManager.getDefaultDisplay();
display.getSize(appUsableSize);
display.getRealSize(realScreenSize);
}
return new Point(realScreenSize.x - appUsableSize.x, realScreenSize.y - appUsableSize.y);
}
}
Simple One-line Solution
As suggested in many of above answers, for example
https://stackoverflow.com/a/29938139/9640177
https://stackoverflow.com/a/26118045/9640177
https://stackoverflow.com/a/50775459/9640177
https://stackoverflow.com/a/41057024/9640177
Simply getting navigation bar height may not be enough. We need to consider whether 1. navigation bar exists, 2. is it on the bottom, or right or left, 3. is app open in multi-window mode.
Fortunately you can easily bypass all the long coding by simply setting android:fitsSystemWindows="true" in your root layout. Android system will automatically take care of adding necessary padding to the root layout to make sure that the child views don't get into the navigation bar or statusbar regions.
There is a simple one line solution
android:fitsSystemWindows="true"
or programatically
findViewById(R.id.your_root_view).setFitsSystemWindows(true);
you may also get root view by
findViewById(android.R.id.content).getRootView();
or
getWindow().getDecorView().findViewById(android.R.id.content)
For more details on getting root-view refer - https://stackoverflow.com/a/4488149/9640177
The height of the bottom Navigation bar is 48dp (in both portrait and landscape mode) and is 42dp when the bar is placed vertically.
Here is how I solved this. I made a hideable bottom bar which needed padding depending on if there was a navigation bar or not (capacitive, on-screen or just pre lollipop).
View
setPadding(0, 0, 0, Utils.hasNavBar(getContext()) ? 30 : 0);
Utils.java
public static boolean hasNavBar(Context context) {
// Kitkat and less shows container above nav bar
if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return false;
}
// Emulator
if (Build.FINGERPRINT.startsWith("generic")) {
return true;
}
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
boolean hasNoCapacitiveKeys = !hasMenuKey && !hasBackKey;
Resources resources = context.getResources();
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
boolean hasOnScreenNavBar = id > 0 && resources.getBoolean(id);
return hasOnScreenNavBar || hasNoCapacitiveKeys || getNavigationBarHeight(context, true) > 0;
}
public static int getNavigationBarHeight(Context context, boolean skipRequirement) {
int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0 && (skipRequirement || hasNavBar(context))) {
return context.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}
In my case where I wanted to have something like this:
I had to follow the same thing as suggested by #Mdlc but probably slightly simpler (targeting only >= 21):
//kotlin
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val realSize = Point()
windowManager.defaultDisplay.getRealSize(realSize);
val usableRect = Rect()
windowManager.defaultDisplay.getRectSize(usableRect)
Toast.makeText(this, "Usable Screen: " + usableRect + " real:"+realSize, Toast.LENGTH_LONG).show()
window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom)
It works on landscape too:
Edit
The above solution does not work correctly in multi-window mode where the usable rectangle is not smaller just due to the navigation bar but also because of custom window size.
One thing that I noticed is that in multi-window the navigation bar is not hovering over the app so even with no changes to DecorView padding we have the correct behaviour:
Note the difference between how navigation bar is hovering over the bottom of the app in these to scenarios.
Fortunately, this is easy to fix. We can check if app is multi window. The code below also includes the part to calculate and adjust the position of toolbar (full solution: https://stackoverflow.com/a/14213035/477790)
// kotlin
// Let the window flow into where window decorations are
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
// calculate where the bottom of the page should end up, considering the navigation bar (back buttons, ...)
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val realSize = Point()
windowManager.defaultDisplay.getRealSize(realSize);
val usableRect = Rect()
windowManager.defaultDisplay.getRectSize(usableRect)
Toast.makeText(this, "Usable Screen: " + usableRect + " real:" + realSize, Toast.LENGTH_LONG).show()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !isInMultiWindowMode) {
window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom)
// move toolbar/appbar further down to where it should be and not to overlap with status bar
val layoutParams = ConstraintLayout.LayoutParams(appBarLayout.layoutParams as ConstraintLayout.LayoutParams)
layoutParams.topMargin = getSystemSize(Constants.statusBarHeightKey)
appBarLayout.layoutParams = layoutParams
}
Result on Samsung popup mode:
In case of Samsung S8 none of the above provided methods were giving proper height of navigation bar so I used the KeyboardHeightProvider keyboard height provider android. And it gave me height in negative values and for my layout positioning I adjusted that value in calculations.
Here is KeyboardHeightProvider.java :
import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;
/**
* The keyboard height provider, this class uses a PopupWindow
* to calculate the window height when the floating keyboard is opened and closed.
*/
public class KeyboardHeightProvider extends PopupWindow {
/** The tag for logging purposes */
private final static String TAG = "sample_KeyboardHeightProvider";
/** The keyboard height observer */
private KeyboardHeightObserver observer;
/** The cached landscape height of the keyboard */
private int keyboardLandscapeHeight;
/** The cached portrait height of the keyboard */
private int keyboardPortraitHeight;
/** The view that is used to calculate the keyboard height */
private View popupView;
/** The parent view */
private View parentView;
/** The root activity that uses this KeyboardHeightProvider */
private Activity activity;
/**
* Construct a new KeyboardHeightProvider
*
* #param activity The parent activity
*/
public KeyboardHeightProvider(Activity activity) {
super(activity);
this.activity = activity;
LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
this.popupView = inflator.inflate(R.layout.popupwindow, null, false);
setContentView(popupView);
setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE | LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
parentView = activity.findViewById(android.R.id.content);
setWidth(0);
setHeight(LayoutParams.MATCH_PARENT);
popupView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (popupView != null) {
handleOnGlobalLayout();
}
}
});
}
/**
* Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
* PopupWindows are not allowed to be registered before the onResume has finished
* of the Activity.
*/
public void start() {
if (!isShowing() && parentView.getWindowToken() != null) {
setBackgroundDrawable(new ColorDrawable(0));
showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
}
}
/**
* Close the keyboard height provider,
* this provider will not be used anymore.
*/
public void close() {
this.observer = null;
dismiss();
}
/**
* Set the keyboard height observer to this provider. The
* observer will be notified when the keyboard height has changed.
* For example when the keyboard is opened or closed.
*
* #param observer The observer to be added to this provider.
*/
public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
this.observer = observer;
}
/**
* Get the screen orientation
*
* #return the screen orientation
*/
private int getScreenOrientation() {
return activity.getResources().getConfiguration().orientation;
}
/**
* Popup window itself is as big as the window of the Activity.
* The keyboard can then be calculated by extracting the popup view bottom
* from the activity window height.
*/
private void handleOnGlobalLayout() {
Point screenSize = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
Rect rect = new Rect();
popupView.getWindowVisibleDisplayFrame(rect);
// REMIND, you may like to change this using the fullscreen size of the phone
// and also using the status bar and navigation bar heights of the phone to calculate
// the keyboard height. But this worked fine on a Nexus.
int orientation = getScreenOrientation();
int keyboardHeight = screenSize.y - rect.bottom;
if (keyboardHeight == 0) {
notifyKeyboardHeightChanged(0, orientation);
}
else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
this.keyboardPortraitHeight = keyboardHeight;
notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
}
else {
this.keyboardLandscapeHeight = keyboardHeight;
notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
}
}
/**
*
*/
private void notifyKeyboardHeightChanged(int height, int orientation) {
if (observer != null) {
observer.onKeyboardHeightChanged(height, orientation);
}
}
public interface KeyboardHeightObserver {
void onKeyboardHeightChanged(int height, int orientation);
}
}
popupwindow.xml :
<?xml version="1.0" encoding="utf-8"?>
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/popuplayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent"
android:orientation="horizontal"/>
Usage in MainActivity
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
/**
* Created by nileshdeokar on 22/02/2018.
*/
class MainActivity : AppCompatActivity() , KeyboardHeightProvider.KeyboardHeightObserver {
private lateinit var keyboardHeightProvider : KeyboardHeightProvider
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
keyboardHeightProvider = KeyboardHeightProvider(this)
parentActivityView.post { keyboardHeightProvider?.start() }
}
override fun onKeyboardHeightChanged(height: Int, orientation: Int) {
// In case of 18:9 - e.g. Samsung S8
// here you get the height of the navigation bar as negative value when keyboard is closed.
// and some positive integer when keyboard is opened.
}
public override fun onPause() {
super.onPause()
keyboardHeightProvider?.setKeyboardHeightObserver(null)
}
public override fun onResume() {
super.onResume()
keyboardHeightProvider?.setKeyboardHeightObserver(this)
}
public override fun onDestroy() {
super.onDestroy()
keyboardHeightProvider?.close()
}
}
For any further help you can have a look at advanced usage of this here.
My version to handle cutouts + navigation bar
fun View.getCutoutRect(): Rect {
return when {
isInEditMode -> {
val cutout = context.dpToPx(16f).roundToInt()
Rect(cutout, cutout, cutout, cutout)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
val windowInsets = (context as? AppCompatActivity)?.window?.decorView?.rootWindowInsets ?: run {
requestLayout()
return Rect()
}
val cutout = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).displayCutout
val systemBars = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).getInsets(WindowInsetsCompat.Type.systemBars())
Rect(
maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
)
}
else -> {
val savedRect = (this.getTag(R.id.view_insets_tag_id) as? Rect) ?: Rect()
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
val cutout = insets.displayCutout
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val rect = Rect(
maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
)
this.setTag(R.id.view_insets_tag_id, rect)
if (savedRect != rect) {
requestLayout()
}
return#setOnApplyWindowInsetsListener insets
}
this.requestApplyInsets()
savedRect
}
}
}
I suggest using the two Context extensions for getting status bar height in px and bottom navigation bar height in dp
Status bar height in dp
val Context.statusBarHeightInDp
get() = run {
val resourceId = this.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
this.resources.getDimensionPixelSize(resourceId) / this.resources.displayMetrics.density
}
Bottom nav bar height in dp
val Context.navBarHeightInDp
get() = run {
val resourceId = this.resources.getIdentifier(
"navigation_bar_height",
"dimen",
"android"
)
this.resources.getDimensionPixelSize(resourceId) / this.resources.displayMetrics.density
}
From Android R (SDK 30+), you can use this code to get size of status bar and navigation bar
WindowInsets insets = activity.getWindowManager().getCurrentWindowMetrics().getWindowInsets();
int statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top; //in pixels
int navigationBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; //in pixels
To obtain the height in the layout XML itself (useful for the last element in a recycler view when clipToPadding is false) you can use the attribute actionBarSize:
android:paddingBottom="?attr/actionBarSize"

Categories

Resources