Modifying System UI Visibility in Android 11

Miloš Černilovský
The Startup
Published in
5 min readSep 18, 2020

When developing Android apps sometimes there is a requirement that the system UI must be hidden and show app in fullscreen. A typical example is a camera app or barcode scanner. In this article I will show how to achieve this with the new WindowInset API, as well as the legacy View.systemUiVisibility so that the code works in Android 11 and is backward compatible with older versions.

Hiding system UI

Below, you can see an example of our camera screen which takes a photo for user’s avatar.

We want to hide the system bars when the Activity is shown. We will create a set of Kotlin extension utility functions so that they can be reused anywhere in the app. First, let’s create a function which hides the UI:

As you can see, for Android 11 and later, the WindowInsetsController is used. The main caveat with the new API is that the default behavior is BEHAVIOR_SHOW_BARS_BY_TOUCH, which basically steals your app’s touches when navigation bar is hidden and a touch anywhere inside the app causes the navigation bar to reappear. However, we want to allow the user taking a photo or touching the camera view for changing focus without showing the bars. That’s why we set BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE. Hiding the bars is simply achieved by calling the hide() method. Since we want the content of the app to be visible under the bars, we also set our custom semitransparent color by setting Window’s navigationBarColor.

For older versions of Android, we achieve hiding status bar by setting the correct mix of flags to the systemUiVisibility of the Window’s decorView. Similarly as above, the View.SYSTEM_UI_FLAG_IMMERSIVE is used for avoiding the system stealing touches. Hiding the navigation and status bar is achieved by View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
and View.SYSTEM_UI_FLAG_FULLSCREEN respectively. We also add View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN. These flags ensure the app content stays behind the bars if user swipes them up. We also add the WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION to the Window so that the navigation bar is semitransparent.

Showing Activity in fullscreen when it is opened

In order to hide all the system UI automatically when the Activity is opened, it is necessary to call our hideSystemUI() extension function from onWindowFocusChanged() when the Activity’s Window gets the focus. Therefore we override the method in the given Activity as follows:

override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) hideSystemUI()
}

Showing system UI

We also want to be able to show the system UI again. Our goal is to achieve showing system bars above the app as in the picture shown below.

Let’s create a new function for showing the UI:

Showing the UI with the new API is achieved by calling WindowInsetsController’s show() method. We also call Window.setDecorFitsSystemWindows(false) so that the content is shown fullscreen under the bars and doesn’t jump.

In older Android versions, we simply set the decorView’s systemUiVisibility to the combination of View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flags. This clears the previous flags which hid the bars and at the same time achieves showing the app UI in the same way as if system bars were hidden, which results in showing app UI under the system bars.

Handling cutouts

As you know, Android supports display cutouts on devices running Android 9 and higher. According to the official article in Android Developers website, the default behavior is that in portrait mode app is shown inside the cutout area unless View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION is set. This means that in Android 10 and lower the app content will “jump” into the cutout when one of the flags is cleared. See an example on the screenshot below.

To avoid this, it is recommended to completely avoid showing the app content inside the cutout areas by using the LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER. So let’s create another extension function:

If we call this function from the given Activity’s onCreate(), the behavior is as we need, i. e. the app’s content doesn’t move if the system bars are shown:

Modifying system UI visibility after user’s input

Finally, we want to toggle the visibility of the system bars after the user clicks some empty area. In order to do this, first we have to know the current status, because the bars can be shown / hidden not only explicitly by our extension functions, but also by the user swiping the bars.

We create a new extension function which automatically registers a listener and takes care of the differences in the used API:

In Android 11 and later, we use the decorView’s setOnApplyWindowInsetsListener. In this case we have to be careful, because the method’s Javadoc states that the given listener takes over the policy for applying window insets and the given View’s onApplyWindowInsets method will not be called. Since we only want to find out the current state without modifying the policy, we just manually call the View’s onApplyWindowInsets method passing the WindowInsets object. Finally, we use the WindowInsets.isVisible method for checking the system bars visibility status. There is one caveat though. In our showSystemUI() and hideSystemUI() extension functions we used WindowInsets.Type.systemBars(), which includes statusBars(), captionBar() and navigationBars(). However, if caption bar is not used, isVisible(WindowInsets.Type.systemBars()) returns false even if status and navigation bars are visible. That’s why check only statusBars() and navigationBars().

In the legacy code we just set listener via decorView’s setOnSystemUiVisibilityChangeListener and check whether the View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag is unset.

Now, all we have to do is set the listener in the Activity:

window.addSystemUIVisibilityListener { 
systemUiVisible = it
}

To toggle the system UI, set a click listener to the View we want to use, for example:

camera_container.setOnClickListener {
toggleSystemUi()
}

Finally, the Activity’s toggleSystemUi() method is very simple:

private fun toggleSystemUi() {
if (systemUiVisible) {
hideSystemUI()
} else {
showSystemUI()
}
}

The API for modifying the system UI can be confusing, especially after the old API has been deprecated and there are not many examples or enough documentation for the new Android 11’s API. Hopefully this article made it easier to understand.

--

--