24

I am using a third-party library and sometimes it pops up a dialog. Before I finish the current activity, I want to check whether there is a dialog popped up in the current context.

Is there any API for this?

Niall C.
  • 10,878
  • 7
  • 69
  • 61
newme
  • 331
  • 1
  • 5
  • 11

9 Answers9

19

You can check it running over the active fragments of that activity and checking if one of them is DialogFragment, meaning that there's a active dialog on the screen:

    public static boolean hasOpenedDialogs(FragmentActivity activity) {
        List<Fragment> fragments = activity.getSupportFragmentManager().getFragments();
        if (fragments != null) {
            for (Fragment fragment : fragments) {
                if (fragment instanceof DialogFragment) {
                    return true;
                }
            }
        }

        return false;
    }
Alécio Carvalho
  • 13,481
  • 5
  • 68
  • 74
  • 2
    Will the dismissed DialogFragment will be removed from FragmentManager – twlkyao Mar 05 '18 at 12:13
  • I see the pop and move moment in dismissInternal, but the method getFragments can only be called from within the same library group. – twlkyao Mar 05 '18 at 12:28
16

I faced a similar problem, and did not want to modify all locations where dialogs were being created and shown. My solution was to look at whether the view I was showing had window focus via the hasWindowFocus() method. This will not work in all situations, but worked in my particular case (this was for an internal recording app used under fairly restricted circumstances).

This solution was not thoroughly tested for robustness but I figured I would post in in case it helped somebody.

Julia Schwarz
  • 2,610
  • 1
  • 19
  • 25
  • This doesn't work reliably when you background the app. In this case hasWindowFocus will return false, even though no dialog is displayed. – tipa Nov 28 '22 at 09:02
11

This uses reflection and hidden APIs to get the currently active view roots. If an alert dialog shows this will return an additional view root. But careful as even a toast popup will return an additional view root.

I've confirmed compatibility from Android 4.1 to Android 6.0 but of course this may not work in earlier or later Android versions.

I've not checked the behavior for multi-window modes.

@SuppressWarnings("unchecked")
public static List<ViewParent> getViewRoots() {

    List<ViewParent> viewRoots = new ArrayList<>();

    try {
        Object windowManager;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            windowManager = Class.forName("android.view.WindowManagerGlobal")
                    .getMethod("getInstance").invoke(null);
        } else {
            Field f = Class.forName("android.view.WindowManagerImpl")
                    .getDeclaredField("sWindowManager");
            f.setAccessible(true);
            windowManager = f.get(null);
        }

        Field rootsField = windowManager.getClass().getDeclaredField("mRoots");
        rootsField.setAccessible(true);

        Field stoppedField = Class.forName("android.view.ViewRootImpl")
                .getDeclaredField("mStopped");
        stoppedField.setAccessible(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            List<ViewParent> viewParents = (List<ViewParent>) rootsField.get(windowManager);
            // Filter out inactive view roots
            for (ViewParent viewParent : viewParents) {
                boolean stopped = (boolean) stoppedField.get(viewParent);
                if (!stopped) {
                    viewRoots.add(viewParent);
                }
            }
        } else {
            ViewParent[] viewParents = (ViewParent[]) rootsField.get(windowManager);
            // Filter out inactive view roots
            for (ViewParent viewParent : viewParents) {
                boolean stopped = (boolean) stoppedField.get(viewParent);
                if (!stopped) {
                    viewRoots.add(viewParent);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return viewRoots;
}
Oliver Jonas
  • 1,188
  • 14
  • 12
  • 1
    Now we just need a way to detect when that action happened. So far it seems that `OnSystemUiVisibilityChangeListener` returns the _first_ time a dialog shows on an Activity, but no subsequent times. I'll keep digging. – Vic Vuci Jun 22 '16 at 20:40
  • Thanks a lot for this! I want to use Firebase Test Lab to run an instrumentation test that navigates through my app and take screenshots on every screen / dialog, but the `cloudtestingscreenshotter_lib.aar` library provided by Google ignores dialogs... Now I'm able to take screenshots of dialogs too! – Mickäel A. Feb 24 '21 at 19:43
10

AFAIK - there is no public API for this.

Recommended way is to have a reference to the dialog, and check for isShowing() and call dismiss() if necessary, but since you're using a third party library, this may not be an options for you.

Your best bet is to check the documentation for the library you use. If that doesn't help, you're out of luck.

Hint: Activity switches to 'paused' state if a dialog pops up. You may be able to 'abuse' this behavior ;)

Madushan
  • 6,977
  • 31
  • 79
  • is there any way to dialog id in onpause? – newme Jun 17 '12 at 07:21
  • 1
    hmm... I see your problem.... You can try overriding the onCreateDialog method. You will have to maintain a list of ids for currently open dialogs and close them when you want to exit. Only use this method if the library you're using doesn't support it. You may run into issues from the third party library if you unexpectedly close their dialogs. (null pointer fun) – Madushan Jun 17 '12 at 21:24
  • Also, please note that this method and dismissDialog method are deprecated in favour of the Fragment pattern. So I wouldn't say it's the best approach. And it seems like you can't detect when a dialog closes (except you can assume it when the activity resumes from a pause. But check to make sure this also happens when resume from a screen off). So you'll have to call 'dismissDialog' on all known dialog ids, which is... well.... I'd say ridiculous. – Madushan Jun 17 '12 at 22:26
  • the problem is i can't get dialog from in onCreateDialog, because it seems the dialog is created in other ways other then showDialog. – newme Jun 19 '12 at 09:27
  • That's weird... Your only option would be to contact developers of the library. Sorry, I can't see any other way around. – Madushan Jun 20 '12 at 02:01
  • thank you anyway. I guess maybe it is the only way to ask the developer. – newme Jun 25 '12 at 15:34
  • but i would like to keep the thread open to see if there is someone else who has the same problem with me. – newme Jun 25 '12 at 15:36
  • 6
    Note that the activity does not switch to paused if a dialog pops up, unless that dialog is actually another activity styled to look like a dialog. See http://stackoverflow.com/a/7384782/25837 – J c Jun 22 '13 at 07:18
9

You can override activity method onWindowFocusChanged(boolean hasFocus) and track the state of your activity.

Normally, if some alert dialog is shown above your activity, the activity does not get onPause() and onResume() events. But it loses focus on alert dialog shown and gains it when it dismisses.

Revertron
  • 1,213
  • 2
  • 13
  • 17
2

For anyone reading this and wondering how to detect a Dialog above fragment or activity, my problem was that inside my base fragment I wanted to detect if I'm displaying a Dialog on top of my fragment. The dialog itself was displayed from my activity and I didn't want to reach it there, so the solution I came up with (Thanks to all answers related to this kind of question) was to get the view (or you can get the view.rootView) of my fragment and check whether any of its children have the focus or not. If none of its children have no focus it means that there is something (hopefully a Dialog) being displayed above my fragment.

// Code inside my base fragment:
val dialogIsDisplayed = (view as ViewGroup).children.any { it.hasWindowFocus() }
Mostafa Arian Nejad
  • 1,278
  • 1
  • 19
  • 32
2

Solution in kotlin

Inside Fragment

val hasWindowFocus = activity?.hasWindowFocus()

In Activity

val hasWindowFocus = hasWindowFocus()

If true, there is no Dialog in the foreground

if FALSE , there is a view/dialog in the foreground and has focus.

Thiago
  • 12,778
  • 14
  • 93
  • 110
1

I am assuming, you are dealing with third party library and you don't have access to dialog object.

You can get the root view from the activity,

Then you can use tree traversal algorithm to see if you can reach any of the child view. You should not reach any of your child view if alert box is displayed.

When alert view is displayed ( check with Ui Automator ), the only element present in UI tree are from DialogBox / DialogActivity. You can use this trick to see if dialog is displayed on the screen. Though it sounds expensive, it could be optimized.

Pradeep
  • 6,303
  • 9
  • 36
  • 60
  • from the root view (DecorView) you cannot reach the Dialog as the dialog is in another window. What can I do is from the DecorVie access the only member that differ which is `mPrivateFlags` and it go from `25201976` to `29363512` when the dialog is open. Next job is to find the right useful flags from here https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/View.java#L1737 should be hard – Hugo Gresse Feb 10 '17 at 10:11
  • hmm, maybe `hasWindowFocus` will do it but I need more test – Hugo Gresse Feb 10 '17 at 11:57
1

If you are using Kotlin just:

supportFragmentManager.fragments.any { it is DialogFragment }
theJosh
  • 2,894
  • 1
  • 28
  • 50