112

How do I maintain the new Immersive Mode when my activities display a custom Dialog?

I am using the code below to maintain the Immersive Mode in Dialogs, but with that solution, the NavBar appears for less than a second when I start my custom Dialog, then it disappears.

The following video explains the issue better (look at the bottom of the screen when the NavBar appears): http://youtu.be/epnd5ghey8g

How do I avoid this behavior?

CODE

The father of all activities in my application:

public abstract class ImmersiveActivity extends Activity {

    @SuppressLint("NewApi")
    private void disableImmersiveMode() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_FULLSCREEN
            );
        }
    }

    @SuppressLint("NewApi")
    private void enableImmersiveMode() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
            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_FULLSCREEN 
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            );
        }
    }


    /**
     * Set the Immersive mode or not according to its state in the settings:
     * enabled or not.
     */
    protected void updateSystemUiVisibility() {
        // Retrieve if the Immersive mode is enabled or not.
        boolean enabled = getSharedPreferences(Util.PREF_NAME, 0).getBoolean(
                "immersive_mode_enabled", true);

        if (enabled) enableImmersiveMode();
        else disableImmersiveMode();
    }

    @Override
    public void onResume() {
        super.onResume();
        updateSystemUiVisibility();
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        updateSystemUiVisibility();
    }

}


All my custom Dialogs call this method in their onCreate(. . .) method:

/**
 * Copy the visibility of the Activity that has started the dialog {@link mActivity}. If the
 * activity is in Immersive mode the dialog will be in Immersive mode too and vice versa.
 */
@SuppressLint("NewApi")
private void copySystemUiVisibility() {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
        getWindow().getDecorView().setSystemUiVisibility(
                mActivity.getWindow().getDecorView().getSystemUiVisibility()
        );
    }
}


EDIT - THE SOLUTION (thanks to Beaver6813, look his answer for more details) :

All my custom dialogs override the show method in this way:

/**
 * An hack used to show the dialogs in Immersive Mode (that is with the NavBar hidden). To
 * obtain this, the method makes the dialog not focusable before showing it, change the UI
 * visibility of the window like the owner activity of the dialog and then (after showing it)
 * makes the dialog focusable again.
 */
@Override
public void show() {
    // Set the dialog to not focusable.
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

    copySystemUiVisibility();

    // Show the dialog with NavBar hidden.
    super.show();

    // Set the dialog to focusable again.
    getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
10
  • How do you show dialogs? Do you use DialogFragments? Apr 9, 2014 at 10:52
  • I don't use DialogFragments but custom Dialog subclasses. developer.android.com/reference/android/app/Dialog.html I show the dialogs simply by calling their show() method.
    – VanDir
    Apr 9, 2014 at 13:50
  • When the dialog appears onWindowFocusChanged is being called. What is the value of enabled when the dialog appears? Is it true or did something went wrong and is it false? Apr 10, 2014 at 19:18
  • Do you mean the onWindowFocusChanged(boolean hasFocus) method of the Dialog class (and not of the Activity class)? In this case the "hasFocus" flag is true.
    – VanDir
    Apr 12, 2014 at 18:08
  • 6
    Did anybody used immersive mode with dialogfragments ?
    – gbero
    Jul 1, 2014 at 14:47

8 Answers 8

180
+200

After a lot of research into the issue there is a hacky fix for this, which involved tearing apart the Dialog class to find. The navigation bar is shown when the dialog window is added to the Window Manager even if you set the UI visibility before adding it to the manager. In the Android Immersive example it's commented that:

// * Uses semi-transparent bars for the nav and status bars
// * This UI flag will *not* be cleared when the user interacts with the UI.
// When the user swipes, the bars will temporarily appear for a few seconds and then
// disappear again.

I believe that's what we're seeing here (that a user-interaction is being triggered when a new, focusable, window view is added to the manager).

How can we work around this? Make the Dialog non-focusable when we create it (so we don't trigger a user-interaction) and then make it focusable after it's displayed.

//Here's the magic..
//Set the dialog to not focusable (makes navigation ignore us adding the window)
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

//Show the dialog!
dialog.show();

//Set the dialog to immersive
dialog.getWindow().getDecorView().setSystemUiVisibility(
context.getWindow().getDecorView().getSystemUiVisibility());

//Clear the not focusable flag from the window
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

Clearly this is not ideal but it seems to be an Android bug, they should check if the Window has immersive set.

I've updated my working test code (forgive the hacky messiness) to Github. I've tested on the Nexus 5 emulator, it will probably blow up with anything less than KitKat but its for proof-of-concept only.

15
  • 3
    Thanks for the sample project but It didn't work. Look what happens in my Nexus 5: youtu.be/-abj3V_0zcU
    – VanDir
    Apr 22, 2014 at 12:24
  • I've updated my answer after some investigation into the issue, it was a tough one! Tested on a Nexus 5 emulator, hopefully this one works.
    – Beaver6813
    Apr 22, 2014 at 22:10
  • 2
    I get 'requestFeature() must be called before adding content' (I think it depends on active features on activity). Solution: Move the dialog.show() one line up so show() is invoked before copying SystemUiVisibility (but after setting non-focusable). Then it still works without jumping nav-bar.
    – arberg
    Jun 27, 2014 at 8:43
  • 6
    @Beaver6813 it doesn't works when EditText is used and softkeyboard is appear in DialogFragment. Do you have nay suggestions to handle it. Sep 16, 2014 at 15:11
  • 1
    Hi Beaver6813 . It does not work when I have an edit text in dialog. Is there any solution?? Dec 29, 2016 at 8:01
40

For your information, thanks to @Beaver6813's answer, I've been able to get this working using DialogFragment.

in the onCreateView method of my DialogFragment, I've just added the following :

    getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
    getDialog().getWindow().getDecorView().setSystemUiVisibility(getActivity().getWindow().getDecorView().getSystemUiVisibility());

    getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            //Clear the not focusable flag from the window
            getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

            //Update the WindowManager with the new attributes (no nicer way I know of to do this)..
            WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
            wm.updateViewLayout(getDialog().getWindow().getDecorView(), getDialog().getWindow().getAttributes());
        }
    });
4
  • 5
    Does this work with the Builder pattern in onCreateDialog()? I've yet to get this hack to work with my DialogFragments, the dialog crashes my app with "requestFeature() must be called before adding content"
    – IAmKale
    Jul 8, 2014 at 0:14
  • 1
    This is brilliant and was the only solution to my usage of DialogFragments. Thank you so much.
    – se22as
    Feb 1, 2018 at 14:20
  • 1
    Great! It works perfectly. Tried so many things, now perfect continues immersive mode.
    – Peterdk
    Jun 3, 2020 at 10:09
  • Thanks. This worked for me without the updating WindowManager part FYI.
    – Adam Johns
    Mar 29, 2023 at 1:20
16

If you want to use onCreateDialog(), try this class. It works pretty well for me...

public class ImmersiveDialogFragment extends DialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
                .setTitle("Example Dialog")
                .setMessage("Some text.")
                .create();

        // Temporarily set the dialogs window to not focusable to prevent the short
        // popup of the navigation bar.
        alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

        return alertDialog;

    }

    public void showImmersive(Activity activity) {

        // Show the dialog.
        show(activity.getFragmentManager(), null);

        // It is necessary to call executePendingTransactions() on the FragmentManager
        // before hiding the navigation bar, because otherwise getWindow() would raise a
        // NullPointerException since the window was not yet created.
        getFragmentManager().executePendingTransactions();

        // Hide the navigation bar. It is important to do this after show() was called.
        // If we would do this in onCreateDialog(), we would get a requestFeature()
        // error.
        getDialog().getWindow().getDecorView().setSystemUiVisibility(
            getActivity().getWindow().getDecorView().getSystemUiVisibility()
        );

        // Make the dialogs window focusable again.
        getDialog().getWindow().clearFlags(
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        );

    }

}

To show the dialog, do the following in your activity...

new ImmersiveDialogFragment().showImmersive(this);
2
  • Any idea on how to stop the NavBar being displayed when an ActionBar menu is shown?
    – William
    Jan 24, 2015 at 23:38
  • I still get NPE, because getDialog() returns null. How did you manage that? Apr 28, 2016 at 21:25
8

Combining the answers here I made an abstract class that works in all cases:

public abstract class ImmersiveDialogFragment extends DialogFragment {

    @Override
    public void setupDialog(Dialog dialog, int style) {
        super.setupDialog(dialog, style);

        // Make the dialog non-focusable before showing it
        dialog.getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
    }

    @Override
    public void show(FragmentManager manager, String tag) {
        super.show(manager, tag);
        showImmersive(manager);
    }

    @Override
    public int show(FragmentTransaction transaction, String tag) {
        int result = super.show(transaction, tag);
        showImmersive(getFragmentManager());
        return result;
    }

    private void showImmersive(FragmentManager manager) {
        // It is necessary to call executePendingTransactions() on the FragmentManager
        // before hiding the navigation bar, because otherwise getWindow() would raise a
        // NullPointerException since the window was not yet created.
        manager.executePendingTransactions();

        // Copy flags from the activity, assuming it's fullscreen.
        // It is important to do this after show() was called. If we would do this in onCreateDialog(),
        // we would get a requestFeature() error.
        getDialog().getWindow().getDecorView().setSystemUiVisibility(
                getActivity().getWindow().getDecorView().getSystemUiVisibility()
        );

        // Make the dialogs window focusable again
        getDialog().getWindow().clearFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        );
    }
}
1
  • since androidx, setupDialog method became a restricted api, how could we deal with that except ignoring it?
    – Minki
    Jul 21, 2020 at 5:06
7

This also works over ride the onDismiss method of your dialog fragment . And within that method call the method of the activity to which it is attached to again set the full screen flags .

@Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        Logger.e(TAG, "onDismiss");
        Log.e("CallBack", "CallBack");
        if (getActivity() != null &&
                getActivity() instanceof LiveStreamingActivity) {
            ((YourActivity) getActivity()).hideSystemUI();
        }
    }

And in your activity add this method :

public void hideSystemUI() {
        // Set the IMMERSIVE flag.
        // Set the content to appear under the system bars so that the content
        // doesn't resize when the system bars hide and show.
        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_STICKY);
    }
1
  • 2
    Most useful comment, as it works great with AlertDialog.Builder and .setOnDismissListener()
    – tomash
    Dec 1, 2017 at 14:04
4

Whean you are creating your own DialogFragment you need only override this method.

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    Dialog dialog = super.onCreateDialog(savedInstanceState);

    dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

    return dialog;
}
3
  • Not really ideal as you wont be able to press anything inside the dialog :(
    – slott
    May 15, 2015 at 11:00
  • Using this my dialogs become unresponsive. But if you insists that it works I'll give it another go.
    – slott
    May 16, 2015 at 10:37
  • @slott you need to clear the WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE after dialog shown
    – 4emodan
    Mar 23, 2016 at 10:48
2

I know this is old post, but my answer may help others.

Below is the hacky fix for Immersive effect in Dialogs:

public static void showImmersiveDialog(final Dialog mDialog, final Activity mActivity) {
        //Set the dialog to not focusable
        mDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
        mDialog.getWindow().getDecorView().setSystemUiVisibility(setSystemUiVisibility());

        mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                //Clear the not focusable flag from the window
                mDialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

                //Update the WindowManager with the new attributes
                WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
                wm.updateViewLayout(mDialog.getWindow().getDecorView(), mDialog.getWindow().getAttributes());
            }
        });

        mDialog.getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
            @Override
            public void onSystemUiVisibilityChange(int visibility) {
                if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                    mDialog.getWindow().getDecorView().setSystemUiVisibility(setSystemUiVisibility());
                }

            }
        });
    }

    public static int setSystemUiVisibility() {
        return 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;
    }
0

As

dialog.window.decorView.setSystemUiVisibility(context.window.decorView.getSystemUiVisibility())

is deprecated in Api 30.

So We need to use new WindowInsetController

Here is new Solution.

These steps will apply for all type of Dialogs and popups to achieve the immersive mode.

  1. You need to make dialog not focusable first.
  2. Show the dialog then.
  3. Hide the navigation and system bars.
  4. Make the dialog focusable again to achieve the focused dialog behavior.

Here is the code snippet in case of Material dialog.

val alertDialogBox = MaterialAlertDialogBuilder(context, R.style.MaterialAlertDialogTheme)
        val dialog = alertDialogBox.create()
        dialog.window?.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
        dialog.show()
        dialog.window?.insetsController?.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
        dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.