17

I'm currently using immersive mode (API 19) for one of my Activities as follows:

getWindow().getDecorView()
            .setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                            | View.INVISIBLE);

This hides the system buttons and notification bar until the user swipes for them back. This works fine, however I wish to detect when the user makes the buttons visible again. I've tried a OnSystemUiVisibilityChangeListener but it does not trigger for this particular event.

Any ideas?

Szymon
  • 42,577
  • 16
  • 96
  • 114
Glitch
  • 2,785
  • 1
  • 27
  • 50
  • Check this for a working solution: https://stackoverflow.com/questions/53509108/how-to-detect-when-the-notification-system-bar-is-opened/53509109#53509109 – The Hungry Androider Nov 28 '18 at 15:44

4 Answers4

9

From Android Developers video, when you're in immersive sticky mode, the app isn't notified.

Immersive sticky mode starts at 6:56 and around 7:25 Roman Nurik tells that the listener won't be triggered.

This is the video: http://youtu.be/cBi8fjv90E4?t=6m56s

  • Hmm, that's annoying. I wonder if there's an unofficial way to detect it. – Glitch Nov 03 '13 at 11:01
  • 1
    I think it's a design flaw that the SYSTEM_UI_FLAG_HIDE_NAVIGATION isn't cleared in sticky mode when the system ui becomes visible. If you have an ui with complex gesture detection there's really no way to distinguish the swipe down (which should be ignored) from other gestures (that should be processed). – Emanuel Moecklin Sep 25 '14 at 18:08
  • I totally agree with that, because it does make a difference if your content is overlayed with a semi-transparent view. I see why they didn't want to have the existing triggers fire, but they could at least have added a new event or a new flag. – Martin C. Mar 20 '15 at 12:47
5

I have found a solution that suits me even though it's not perfect. I set the UI visiblity to View.SYSTEM_UI_FLAG_IMMERSIVE instead of View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY and when I receive the onSystemUiVisibilityChange callback, I delay a message to an Handler to reset the UI Visibility. Here is the code :

private static final int FULL_SREEN_MSG = 10;
private static final long TIME_BEFORE_HIDE_UI = 2 * DateUtils.SECOND_IN_MILLIS;

private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == FULL_SREEN_MSG) {
            setFullscreen();
        }
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setFullscreen();
    getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
}

private void setFullscreen() {
    getWindow().getDecorView().setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
                    | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
                    | View.SYSTEM_UI_FLAG_IMMERSIVE);
}

@Override
public void onSystemUiVisibilityChange(int visibility) {
    if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
        mHandler.sendEmptyMessageDelayed(FULL_SREEN_MSG, TIME_BEFORE_HIDE_UI);
    }
}
Benjamin M.
  • 604
  • 6
  • 13
  • why do you delay before calling setFullScreen? – Damian Helme Oct 03 '15 at 10:47
  • 1
    Because if I don't, it will re-hide the status bar directly and I wanted to mimic the way View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY is working. – Benjamin M. Oct 03 '15 at 10:51
  • ok, thanks. I sometimes get a funny condition where onSystemUiVisibilityChange seems to get called multiple times. I was wondering if your delay was to fix that. – Damian Helme Oct 05 '15 at 19:08
4

UPDATED ANSWER:

Set an OnSystemUiVisibilityChangeListener, force immersive mode when visibility is 0 (rather than 6).

if(android.os.Build.VERSION.SDK_INT >= 19) {
        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                if(visibility == 0) {
                    getWindow().getDecorView().setSystemUiVisibility(
                            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
                }
            }
        });
    }

OLD, NASTY ANSWER:

this is nasty, but it is a solution IFF you were showing an ActionBar:

  • add an OnGlobalLayoutListener to the ViewTreeObserver of 'action_bar_container'.
  • in the OnGlobalLayoutListener implementation, check if 'action_bar_container' visibility if GONE or not.
  • when it moved from GONE to !GONE (and assuming you were in immersive mode) then force immersive mode again via the setSystemUiVisibility method.

if(android.os.Build.VERSION.SDK_INT >= 19) {
        int actionBarContainerId = Resources.getSystem().getIdentifier("action_bar_container", "id", "android");
        ((ViewGroup)findViewById(actionBarContainerId)).getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int actionBarContainerId = Resources.getSystem().getIdentifier("action_bar_container", "id", "android");
                ViewGroup actionBarContainer = (ViewGroup) findViewById(actionBarContainerId);
                if(actionBarContainer.getVisibility() == View.GONE) {
                    if(DEBUG) Log.d(TAG, "...PROBABLY IN IMMERSIVE MODE AND ALL IS GOOD!..");
                } else {
                    if(DEBUG) Log.d(TAG, "...PROBABLY NO LONGER IN IMMERSIVE MODE, HEY..");
                    getWindow().getDecorView().setSystemUiVisibility(
                            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            | View.SYSTEM_UI_FLAG_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
                }

            }
        });
    }
straya
  • 5,002
  • 1
  • 28
  • 35
  • Does it work when the user change the device volume with hardware buttons and the dialog appears above the app? In my tests, if the first thing the user does after opening the immersive app is changing the volume with hardware buttons, the app visibility is not changed. – mmathieum Mar 13 '14 at 15:00
3

In 4.4, the app will not receive any indication when the transient system bars are revealed or auto-hidden (under IMMERSIVE_STICKY), either via the OnSystemUiVisibilityChangeListener or other means.

You can listen for edge swipes similar to the system gesture listener as a guess, but this is not part of the public api, it may change in future releases and differ across devices.

I'm curious what you want to do when the transient system bars are shown/hidden.

jspurlock
  • 1,466
  • 10
  • 7
  • Thanks I'll give that a go. I'm just playing around with visual effects at this point, there's no exact aim at the moment. – Glitch Nov 04 '13 at 06:52