58

I am trying to check to see whether the android navigation bar is present on load so that I can adjust a layout accordingly, does anyone have any suggestions?

This is the navigation bar I am trying to detect: enter image description here

P.S. All I have found so far are 'bad' ways to try and remove the bar, which I dont want to do.

Frank
  • 16,476
  • 7
  • 38
  • 51
Jonno
  • 1,542
  • 3
  • 22
  • 39
  • 4
    Why do you care whether or not the bar is there? After all, the screen size itself may be completely different than any other given device, regardless of the bar. Then there are devices like the Kindle Fire series, with a different bar. Shouldn't you be making decisions based on available space for your UI, rather than on whether the navigation bar exists? – CommonsWare Apr 18 '13 at 20:29
  • When test on an S3 running 4.1 the navigation bar isn't present to which the layout is working flawless, testing on an Nexus 4 running 4.1 with the navigation bar, some of the layout near the bottom is slightly cut off. Both screens 1280x720 xhdpi – Jonno Apr 18 '13 at 20:36
  • When your app runs on another screen with another resolution and density, you will have yet different results, having little to do with whether or not there is a navigation bar. – CommonsWare Apr 18 '13 at 20:38
  • As an observation, Samsung devices without the system nav bar are listed as "long" and HTC and Motorola devices with the nav bar are listed as "notlong" -- that would allow using regular resource loading to determine differing layouts. However, this does not strictly match the definition of long vs notlong, which is supposed to just be screen aspect. Turns out, it's the "usable dp aspect" (see http://stackoverflow.com/a/17935955/91165 and look at the Android code carefully). – lilbyrdie May 28 '14 at 13:14
  • 1
    @CommonsWare my use case involves an immersive video player which can show/hide the SystemNavBar. A progressBar + SystemNavBar displays onTap, and both disappear on another tap. To prevent the SystemNav bar from overlapping the progressBar, I need to set the correct margins to the size of the SystemNavBar, including the case when the SystemNavBar does not exists. I bring this up as an example of when it's useful to determine if a SystemNavBar exists. – sudocoder Jun 23 '15 at 16:29
  • If you need to know the exact size, you're doing layouts completely wrong. This is unnecessary, even for full screen media players. Check the official API Demos. – Paul Burke Aug 03 '15 at 10:39

12 Answers12

52

Took me some time but I've found a more reliable way than relying on hasPermanentMenuKey() which doesn't work for newer phones like the HTC One which have no menu key but do have home & back keys so don't need (or show) the soft navigation bar. To get around this try the following code which checks for a back button too:

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

if(!hasMenuKey && !hasBackKey) {
    // Do whatever you need to do, this device has a navigation bar
}
philask
  • 790
  • 9
  • 12
  • 8
    On some devices with soft keys (like Galaxy Tab 2) KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK) returns true. – zarej Nov 21 '13 at 11:09
  • 4
    @zarej - Samsung, what can you do! – philask Dec 09 '13 at 06:19
  • But if the menu key isn't part of the navigation bar, why do you check for it anyway? Also, wouldn't it be wise to also check for `KeyEvent.KEYCODE_HOME`? That might solve the problem with the Galaxy Tab 2. – Rudey Apr 20 '14 at 10:44
  • The navigation bar on devices such as the OnePlus One can't be detected, since the navigation bar is optional (physical keys also present). My answer below has the same problem. – Rudey Sep 01 '14 at 12:02
  • Nice Answer.Can you please let me know is it possible to detect position of Back button either right hand side or left hand side on device? – AndiM Sep 10 '14 at 05:26
  • 4
    Hi, on Lollipop hasBackKey is true even for devices with NavBar - any ideas how to fix that? – daemontus Jan 16 '15 at 17:24
  • Its not working on htc 616 ...It returns true but device dnt have any physical button..plz help me – Singh Arjun Jan 20 '15 at 11:46
  • 2
    This does not work for me, Nexus5 returns true for hasMenuKey and hasBackKey therefore it thinks there is no navBar. (Nexus7 correctly returns false for hasMenuKey and hasBackKey). A better solution which worked for me is : http://stackoverflow.com/a/29609679/2663916 – se22as Jun 10 '15 at 11:31
35

There's no reliable way to check for a navigation bar. Using KeyCharacterMap.deviceHasKey you can check if certain physical keys are present on the device, but this information is not very useful since devices with physical keys can still have a navigation bar. Devices like the OnePlus One, or any device running a custom rom, have an option in the settings that disables the physical keys, and adds a navigation bar. There's no way to check if this option is enabled, and deviceHasKey still returns true for the keys that are disabled by this option.

This is the closest you can get:

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
}

If the back and home button are not both physically present on the device, it must have a navigation bar, because the user otherwise wouldn't be able to navigate at all. However, you can never be 100% sure about this, since manufacturers can implement deviceHasKey wrong.

Rudey
  • 4,717
  • 4
  • 42
  • 84
  • Is this possible to implement the by manufacture ID with the double check of the above coding? if so how do you work with that? – Soma Hesk Jul 02 '15 at 01:32
  • @jRhesk checking by manufacture ID doesn't sound like a good idea, but it might work. I wouldn't how to do such thing in the Android SDK. – Rudey Jul 02 '15 at 12:31
  • I have checked with the code above with OnePlus and the valuables for both of them are always false even i have turned on the on screen nagivation bar on the bottom. This is happening on Oneplus A0001. How do I get an work around that will work with different vendors by case? I think it should be an external sdk or library to determine whether the current device has the on screen navigation menu on or not. – Soma Hesk Jul 06 '15 at 06:15
12

Another solution (a part of my class UtilsUISystem )

    public static boolean hasNavBar (Resources resources)
    {
        //Emulator
        if (Build.FINGERPRINT.startsWith("generic"))
            return true;

        int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
        return id > 0 && resources.getBoolean(id);
    }
Pauland
  • 1,974
  • 16
  • 25
  • 1
    This also did not work for me, as values are returned for that resource even on devices which do not have a navbar (e.g. samsung devices). A better solution which worked for me is : http://stackoverflow.com/a/29609679/2663916 – se22as Jun 10 '15 at 11:32
  • @se22as this code is good for me. I've tested on Galaxy S5 and a lot of Nexus. If "config_showNavigationBar" identifier exist, the code return the value set by the manufacturer. else, this identifier do not exist, there is no navigationbar (pre-honeycomb) – Pauland Jun 10 '15 at 15:19
9

Here is a quick answer that combines Pauland's and Philask's solutions. I'm afraid I don't have enough devices available to test if it works everywhere, though. I'd be interested to hear others' results.

boolean hasNavBar(Context context) {
    Resources resources = context.getResources();
    int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
    if (id > 0) {
        return resources.getBoolean(id);
    } else {    // Check for keys
        boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
        boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
        return !hasMenuKey && !hasBackKey;
    }
}
Tad
  • 4,668
  • 34
  • 35
  • 2
    I tried using this and unfortunately on a Nexus 5 it is still incorrect. the "resources.getBoolean(id)" is return false even though the nav bar is present. – se22as Jun 09 '15 at 12:57
  • 1
    @se22as Was the Nexus 5 you referred to a "real" device, or an emulator? I do see on a N5 Lollipop Android emulator that "config_showNavigationBar" returns false. I'm hoping that isn't true in the real world... – GaryAmundson Jan 22 '16 at 20:29
  • I can confirm that this _does_ return true on an actual Nexus 5 device – slott Oct 22 '18 at 12:23
7

I've done like this, it works on every device I tested, and even on emulators:

public static boolean hasNavigationBar(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());
}
Matteo
  • 135
  • 2
  • 7
5

you could add this code to your activity's onCreate() method:

 View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
        (new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
                    // TODO: The navigation bar is visible. Make any desired
                    // adjustments to your UI, such as showing the action bar or
                    // other navigational controls.
                } else {
                    // TODO: The navigation bar is NOT visible. Make any desired
                    // adjustments to your UI, such as hiding the action bar or
                    // other navigational controls.
                }
            }
        });
joe
  • 619
  • 6
  • 10
  • That doesn't work on Samsung Galaxy 5 tables. That device has a hardware nav bar and these callbacks are still made even though the bar can't possibly move. – Monte Creasor May 27 '18 at 06:02
3

This method worked for me

    int id = getResources().getIdentifier("config_showNavigationBar","bool","android");
        boolean result = id > 0 && getResources().getBoolean(id);
//
        if(result) {

            // Do whatever you need to do, this device has a soft Navigation Bar
        }

It worked for me and tested in many devices.

ANAND SONI
  • 625
  • 9
  • 13
2

Something that should probably work better is to measure the screen.

Starting with API 17 there's getWindowManager().getDefaultDisplay().getRealSize(), which can be compared to size returned by getWindowManager().getDefaultDisplay().getSize().

If you get different results I think it's safe to say that there is a nav bar and if you get the same results there isn't one. One thing to pay attention to is your target SDK and supported screens, which might cause the result of getSize() to be scaled if Android thinks your app wouldn't work well on the current device without scaling.

Below API 17 you can measure the screen via getWindowManager().getDefaultDisplay().getMetrics() in both landscape and portrait mode, and again, different results probably mean there's a nav bar.

However, if you get the same results, you don't actually know, as phones can keep the nav bar on the shorter edge even when in landscape. An educated guess would be that if either the width or the height is 4% to 8% smaller than standard sizes like 1280x800, 1280x720, 1024x600, while the other dimension is equal, then again there probably is a nav bar. Don't bet on it, though. There are too many resolutions, which differ too little from one another for this to work well.

Soma
  • 861
  • 2
  • 17
  • 32
ciobi
  • 97
  • 1
  • 10
  • If you try this approach from a service, you can get the wrong result if you do the check while in fullscreen (i.e youtube or a media player). just a note. – Vlad Jan 04 '16 at 10:42
  • Even though this works by side-effect, it makes a lot of sense and seems to give the right results. +1 – SMBiggs Aug 07 '19 at 05:23
2
boolean hasNavBar(Context context) {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        // navigation bar was introduced in Android 4.0 (API level 14)
        Resources resources = context.getResources();
        int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
        if (id > 0) {
            return resources.getBoolean(id);
        } else {    // Check for keys
            boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
            boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
            return !hasMenuKey && !hasBackKey;
        }
    } else {
        return false;
    }
}
Kishan Vaghela
  • 7,678
  • 5
  • 42
  • 67
1

On Android 10 (API level 29), you can also check for the bottom window inset:

@RequiresApi(api = Build.VERSION_CODES.Q)
private boolean hasNavigationBar() {
    final WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
    if (windowInsets == null) {
            throw new RuntimeException("Window is not attached");
    }
    return windowInsets.getTappableElementInsets().bottom > 0;
}

Note that the window has to be attached for getRootWindowInsets() to return a non-null value, so you likely want to call this in onAttachedToWindow.

This solution is also used by LineageOS's launcher app Trebuchet (source), which is how I learned of it.

user2623008
  • 311
  • 3
  • 13
0

I see the answers above, I want to indicate that the "not exist" can be regard as the height of 0; so it can be like this:

public static int getScreenH(Context context) {
    DisplayMetrics dm = new DisplayMetrics();
    dm = context.getResources().getDisplayMetrics();
    int h = dm.heightPixels;
    return h;
}

public static int getDpi(Context context) {

    DisplayMetrics displayMetrics1 = context.getResources().getDisplayMetrics();
    int height1 = displayMetrics1.heightPixels;

    int dpi = 0;
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = windowManager.getDefaultDisplay();
    DisplayMetrics displayMetrics = new DisplayMetrics();

    @SuppressWarnings("rawtypes")
    Class c;
    try {
        c = Class.forName("android.view.Display");
        @SuppressWarnings("unchecked")
        Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
        method.invoke(display, displayMetrics);
        dpi = displayMetrics.heightPixels;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return dpi;
}

public static int getBottomStatusHeight(Context context) {
    int totalHeight = getDpi(context);

    int contentHeight = getScreenH(context);

    return totalHeight - contentHeight;
}

```

-2

Solution: Only devices without permanent hardware keys have the navigation bar hence you can check for the API version and use hasPermanentMenuKey() to find hardware keys

 boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
Jonno
  • 1,542
  • 3
  • 22
  • 39
  • 1
    As explained by other solutions, this is quite unreliable -> no menu key but back, home and multitasking keys. – dst Nov 25 '14 at 07:30