Shared publicly  - 
 
I previously mentioned a little about an annoying oversight that Google made regarding the immersive functionality of Kitkat... The workaround for the Kitkat IMMERSIVE_STICKY "non-sticky sticky" situation is fairly straight forward... In my case I have a portrait locked / non-game app that also has an action bar.  On 4.4.x it turns out that if the user selects the volume up / down buttons or also happens to press the default overflow button in the action bar that the immersive sticky attribute of the system UI, in particular the navigation bar, is erased. I also just noticed the sticky flag gets reset when a DialogFragment / Dialog is launched; doh, apparently a fair amount of normal app control flow can reset the sticky state.

This is really annoying because it makes the navigation bar opaque instead of transparent and covers any UI components below where the navigation bar appears, so visually it's unpleasant. It also cancels the automatic hiding of the navigation bar that is expected when setting the system UI to immersive sticky. Unless the app developer explicitly enables the immersive sticky flag again the navigation bar will stay visible. This of course breaks the contract of the API and the whole point of the immersive sticky flag. Technically in the first place the navigation bar should not appear for any user action unless the user swipes up from the bottom. 

One can set the system UI visibility with a number of different parameters, but I just use two in my current app:

View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY

getWindow().getDecorView().setSystemUiVisibility(systemUIFlags)

In the onCreate method I of course call the above line to set the initial immersive sticky state.  I also track if the sticky flag is set via:

boolean isImmersiveSticky;

isImmersiveSticky = (systemUIFlags & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;

To reverse the volume up / down and overflow action button cancellation of immersive sticky.. Simply create a Runnable in onCreate like the following:

immersiveRunnable = new Runnable()
{
   @Override
   public void run()
   {
 getWindow().getDecorView().setSystemUiVisibility(systemUIFlags);
    }
};

In your Activity if there is an ActionBar then capture the overflow button press in "onPrepareOptionsMenu"

@Override
public boolean onPrepareOptionsMenu(Menu menu)
{
   if (isImmersiveSticky)
   {
getWindow().getDecorView().postDelayed(immersiveRunnable, 500);
   }
   return super.onPrepareOptionsMenu(menu);
}

To capture the volume up and down buttons:

public boolean onKeyDown(int keyCode, KeyEvent event)
{
   if (isImmersiveSticky && (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP))
   {
getWindow().getDecorView().postDelayed(immersiveRunnable, 500);
   }
   return super.onKeyDown(keyCode, event);
}

-----

UPDATE...  In my particular app I also launch a DialogFragment / Dialog from a menu option in the overflow of the ActionBar.  Launching the DialogFragment also overrides the immersive sticky flag and will cause the navigation bar to appear and not go away, so depending on your app it may also be necessary to set the system UI flags in 

public boolean onOptionsItemSelected(MenuItem item)
{
  <HANDLE MENU ITEM SELECTION HERE>

   if (isImmersiveSticky)
   {
getWindow().getDecorView().postDelayed(immersiveRunnable, 500);
   }
   return super.onOptionsItemSelected(item);
}


-------

Why would this happen in the first place? Well, partly it's a failure of OOP and the tangled mess that underlies the Android SDK. Clearly the state tracking of the system UI visibility flags is getting overwritten in code that is triggered by the volume up / down buttons, the default ActionBar overflow button, launching a DialogFragment, and potentially many other normal app control flow situations. Whoever implemented the immersive sticky functionality simply didn't test thoroughly to see where / when the state got overridden.  In the case of OOP and code in diverse areas that may modify state sometimes it's hard to know where these changes will occur. However, I'm surprised this wasn't caught in simple visible QA / testing because it's so obvious there is an issue! 

-------

I suppose one really nice thing about TyphonRT, my component oriented middleware framework, is that I was able to easily separate the conditional handling above into a "system component" and only load via external configuration files the system component into an Activity if on Kitkat / API level 19 if the given Activity needs this sort of handling at all. In TyphonRT an Activity is also a container which can hold any sort of data or system component. IE one doesn't have to embed conditional API level checks directly in an inherited Activity and override explicitly onKeyDown / onPrepareOptionsMenu in every immersive sticky Activity in ones app.  Simply load the component if it's needed. 

Well... The world is not ending, but dang immersive sticky functionality is ugly on Kitkat.. Please fix it Google.. There are other more nefarious bugs too like the multi-account one as previously mentioned.
5
1
Brad Wells's profile photoMichael Leahy's profile photoHossein Zolfagharian's profile photoSteve Adams's profile photo
4 comments
 
So it seems to get any GUI views that are bottom aligned to not be affected by the navigation bar becoming heavyweight when it becomes non-sticky that a hard coded RelativeLayout for the bottom most GUI view or two is necessary. This is the only way I have found so far to prevent the Navigation bar from readjusting the bottom most GUI view up when it becomes temporarily heavyweight / non-sticky. 

This is somewhat a PITA for non game apps. If only ViewStub was upgraded to support the "merge" directive then things could be configured a little more cleanly. Right now I'm using some dimension resources to handle variations in this forced layout to deal with any differences while keeping one layout.

This isn't so much a problem with a fullscreen game that uses SurfaceView / OpenGL because the underlying Surface is not affected by heavyweight components. That and / or one can of course set things to "fill_parent" for layout width / height for the SurfaceView and the temporary heavyweight nav bar can't adjust the view / game screen.   For non-full screen apps it's a PITA.
 
Great workaround.  Works perfectly.  Thanks for sharing
 
Thanks Brad... 

I just came across another very annoying corner case when immersive sticky state is reset and that is when the virtual keyboard is shown say in connection with a user selecting an EditText view. 

Unfortunately, detecting the virtual keyboard visibility is not possible with Android at least in a non-hacky / clean code kind of way... I detailed my solution which is particular to TyphonRT here. 

https://plus.google.com/+MichaelLeahy/posts/TdFqSmxeMfx
Add a comment...