How do I maintain the Immersive Mode in Dialogs?

34,019

Solution 1

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.

Solution 2

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());
        }
    });

Solution 3

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);

Solution 4

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
        );
    }
}

Solution 5

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);
    }
Share:
34,019

Related videos on Youtube

VanDir
Author by

VanDir

Updated on July 18, 2022

Comments

  • VanDir
    VanDir almost 2 years

    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);
    }
    
    • Tadeas Kriz
      Tadeas Kriz about 10 years
      How do you show dialogs? Do you use DialogFragments?
    • VanDir
      VanDir about 10 years
      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.
    • Kevin van Mierlo
      Kevin van Mierlo about 10 years
      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?
    • VanDir
      VanDir about 10 years
      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.
    • Ruzanna
      Ruzanna about 10 years
      Hi @VanDir, did you found any solutions. I'm stuck into this problem as well. Please, let me know.
    • VanDir
      VanDir about 10 years
      Me too. I can't release an update to my game for this problem.
    • Ando Masahashi
      Ando Masahashi about 10 years
      @VanDir can you explain more via images?
    • VanDir
      VanDir about 10 years
      I edited the answer with a video of the issue.
    • gbero
      gbero almost 10 years
      Did anybody used immersive mode with dialogfragments ?
    • tomash
      tomash over 6 years
      Least intrusive answer is stackoverflow.com/questions/22794049/… as it doesn't prevent showing softbutton bar but removes it along with Dialog
  • VanDir
    VanDir about 10 years
    Thanks for the sample project but It didn't work. Look what happens in my Nexus 5: youtu.be/-abj3V_0zcU
  • Beaver6813
    Beaver6813 about 10 years
    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.
  • VanDir
    VanDir about 10 years
    You're a genius! It worked. Thank you! Only one thing: I noticed that the solution works also without the last 2 lines. Why did you add them?
  • Beaver6813
    Beaver6813 about 10 years
    Being overly cautious! I wasn't sure if clearing the flag on the window would update it after being added to the manager, evidently it does so I'll update the answer.
  • Toni Alvarez
    Toni Alvarez almost 10 years
    Thanks! Works like a charm!!
  • arberg
    arberg almost 10 years
    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.
  • IAmKale
    IAmKale almost 10 years
    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"
  • androidcodehunter
    androidcodehunter over 9 years
    @Beaver6813 it doesn't works when EditText is used and softkeyboard is appear in DialogFragment. Do you have nay suggestions to handle it.
  • William
    William over 9 years
    Any idea on how to stop the NavBar being displayed when an ActionBar menu is shown?
  • William
    William over 9 years
    Any idea on how to stop the NavBar being displayed when an ActionBar menu is shown?
  • Menna-Allah Sami
    Menna-Allah Sami about 9 years
    @androidcodehunter do you find a solution for your problem ?
  • Menna-Allah Sami
    Menna-Allah Sami about 9 years
    Can I find a similar solution but for softkeyboard ?
  • slott
    slott almost 9 years
    Not really ideal as you wont be able to press anything inside the dialog :(
  • slott
    slott almost 9 years
    Using this my dialogs become unresponsive. But if you insists that it works I'll give it another go.
  • 4emodan
    4emodan about 8 years
    @slott you need to clear the WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE after dialog shown
  • Anton Shkurenko
    Anton Shkurenko about 8 years
    I still get NPE, because getDialog() returns null. How did you manage that?
  • Denys Vitali
    Denys Vitali almost 8 years
    Works in Android N Dev Preview 2 on a Google Pixel C, thank you!
  • Navin Gupta
    Navin Gupta over 7 years
    Hi Beaver6813 . It does not work when I have an edit text in dialog. Is there any solution??
  • tomash
    tomash over 6 years
    Most useful comment, as it works great with AlertDialog.Builder and .setOnDismissListener()
  • se22as
    se22as about 6 years
    This is brilliant and was the only solution to my usage of DialogFragments. Thank you so much.
  • Alessandro Caliaro
    Alessandro Caliaro about 6 years
    Therre is a solution when spinner popup?
  • Droidman
    Droidman over 5 years
    Can confirm that this still works. I was fighting the same issue recently, in case anyone's interested I did generalize this solution for an AlertDialog (Kotlin): gist.github.com/vad-zuev/c8e0f176af24837ba9c3fd420d915d14
  • dragonfly
    dragonfly over 5 years
    @Beaver6813 You solved the Dialog issue, but how to solve solve PopupWindow issue?
  • Peterdk
    Peterdk about 5 years
    This solution works great for dialogs with not EditText in them. However, with a EditText there is no way to restore focus and all keyboard input is directed to the view below the dialog. Did anybody got to fix that?
  • Peterdk
    Peterdk almost 4 years
    Great! It works perfectly. Tried so many things, now perfect continues immersive mode.
  • Minki
    Minki almost 4 years
    since androidx, setupDialog method became a restricted api, how could we deal with that except ignoring it?