androidandroid-fullscreenandroid-immersiveandroid-navigation-bar

How to fix : Navigation bar icons still showing, when pop-up menu is opened (Full screen - Immersive sticky)


I'm trying to achieve a full-screen application, where the user doesn't have any access to the status- & navigation-bar.

Preferably I would want them to be removed completely, but from what I've read this is not possible unless you root the device

So my question basically is: How do I hide the navigation-bar icons, when I show the pop-up menu?

BEFORE

BEFORE

AFTER

AFTER

So far, I have tried:

ACTIVITY


    class PinCodeActivity, HasTitleBar {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_pin_code)
            initTitleBarWith(this)
            hideNavigation()
        }

        override fun onResume() {
            super.onResume()
            hideNavigation()
        }

        fun hideNavigation() {
            window.decorView.apply {
                systemUiVisibility = FLAGS
            }
        }

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

const val FLAGS = (View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)

TITLE-BAR


    fun HasTitleBar.initTitleBarWith(activity: Activity, resId: Int = R.id.titleBar) {
        val langButton = activity.findViewById<View>(resId).findViewById<Button>(R.id.tbLanguageChoiceBtn)
        val wrapper = ContextThemeWrapper(activity, R.style.MyPopupMenu)
        val popupMenu = PopUpLanguageMenu(wrapper, langButton)
        langButton.setOnClickListener {
            activity.hideNavigation()
            popupMenu.showMenu()
            activity.hideNavigation()
        }
    }

POP-UP MENU


    class PopUpLanguageMenu constructor(context: Context, view: View) : PopupMenu(context, view) {

        private var popupHelper: MenuPopupHelper

        init {
            val popUpMenu = PopupMenu(context, view).apply {
                inflate(R.menu.menu_language_dropdown)
            }

            popupHelper = MenuPopupHelper(context, popUpMenu.menu as MenuBuilder, view)
            popupHelper.run {
                gravity = Gravity.END
                setForceShowIcon(true)
            }
        }

        fun showMenu() {
            popupHelper.show()
        }
    }

EXPECTED RESULT: Navigation-bar & it's icons are hidden, after the pop-up menu is shown, the icons are still HIDDEN

ACTUAL RESULT: Navigation-bar & it's icons are hidden, after the pop-up menu is shown, the icons are SHOWN


Solution

  • The nav bar re-appears because there is a new DecorView (PopupDecorView) is newly drawn on the top of the Views stack, which isn't affected by your FLAGs set before.

    There is no silver bullet here, my approach is to dig into the WindowManagerGlobal by reflection and catch out the peek View, apply the system FLAGs on it again, so after the PopupMenu shows up, it manages to hide the Navigation bar right after that (there is still, a show up from the Navigation Bar once).

    Here is the code:

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if (hasFocus) {
            hideNavigation()
        } else {
            // When PopupMenu appears, the current Activity looses the focus
            setFlagsOnThePeekView() // Hijack to the current peek view, apply the Flags on it
        }
    }
    
    @SuppressLint("PrivateApi")
    fun setFlagsOnThePeekView() {
        try {
            val wmgClass = Class.forName("android.view.WindowManagerGlobal")
            val wmgInstance = wmgClass.getMethod("getInstance").invoke(null)
            val viewsField = wmgClass.getDeclaredField("mViews")
            viewsField.isAccessible = true
    
            val views = viewsField.get(wmgInstance) as ArrayList<View>
            // When the popup appears, its decorView is the peek of the stack aka last item
            views.last().apply { 
                systemUiVisibility = FLAGS
                setOnSystemUiVisibilityChangeListener {
                    systemUiVisibility = FLAGS
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }