1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.DrawableRes;
21 import android.annotation.IdRes;
22 import android.annotation.LayoutRes;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.StringRes;
26 import android.annotation.StyleRes;
27 import android.compat.annotation.UnsupportedAppUsage;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.ContextWrapper;
31 import android.content.DialogInterface;
32 import android.content.pm.ApplicationInfo;
33 import android.content.res.Configuration;
34 import android.content.res.Resources;
35 import android.graphics.drawable.Drawable;
36 import android.net.Uri;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.util.Log;
43 import android.util.TypedValue;
44 import android.view.ActionMode;
45 import android.view.ContextMenu;
46 import android.view.ContextMenu.ContextMenuInfo;
47 import android.view.ContextThemeWrapper;
48 import android.view.Gravity;
49 import android.view.KeyEvent;
50 import android.view.LayoutInflater;
51 import android.view.Menu;
52 import android.view.MenuItem;
53 import android.view.MotionEvent;
54 import android.view.SearchEvent;
55 import android.view.View;
56 import android.view.View.OnCreateContextMenuListener;
57 import android.view.ViewGroup;
58 import android.view.ViewGroup.LayoutParams;
59 import android.view.Window;
60 import android.view.WindowManager;
61 import android.view.accessibility.AccessibilityEvent;
62 
63 import com.android.internal.R;
64 import com.android.internal.app.WindowDecorActionBar;
65 import com.android.internal.policy.PhoneWindow;
66 
67 import java.lang.ref.WeakReference;
68 
69 /**
70  * Base class for Dialogs.
71  *
72  * <p>Note: Activities provide a facility to manage the creation, saving and
73  * restoring of dialogs. See {@link Activity#onCreateDialog(int)},
74  * {@link Activity#onPrepareDialog(int, Dialog)},
75  * {@link Activity#showDialog(int)}, and {@link Activity#dismissDialog(int)}. If
76  * these methods are used, {@link #getOwnerActivity()} will return the Activity
77  * that managed this dialog.
78  *
79  * <p>Often you will want to have a Dialog display on top of the current
80  * input method, because there is no reason for it to accept text.  You can
81  * do this by setting the {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
82  * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} window flag (assuming
83  * your Dialog takes input focus, as it the default) with the following code:
84  *
85  * <pre>
86  * getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
87  *         WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);</pre>
88  *
89  * <div class="special reference">
90  * <h3>Developer Guides</h3>
91  * <p>For more information about creating dialogs, read the
92  * <a href="{@docRoot}guide/topics/ui/dialogs.html">Dialogs</a> developer guide.</p>
93  * </div>
94  */
95 public class Dialog implements DialogInterface, Window.Callback,
96         KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
97     private static final String TAG = "Dialog";
98     @UnsupportedAppUsage
99     private Activity mOwnerActivity;
100 
101     private final WindowManager mWindowManager;
102 
103     @UnsupportedAppUsage
104     final Context mContext;
105     @UnsupportedAppUsage
106     final Window mWindow;
107 
108     View mDecor;
109 
110     private ActionBar mActionBar;
111     /**
112      * This field should be made private, so it is hidden from the SDK.
113      * {@hide}
114      */
115     protected boolean mCancelable = true;
116 
117     private String mCancelAndDismissTaken;
118     @UnsupportedAppUsage
119     private Message mCancelMessage;
120     @UnsupportedAppUsage
121     private Message mDismissMessage;
122     @UnsupportedAppUsage
123     private Message mShowMessage;
124 
125     @UnsupportedAppUsage
126     private OnKeyListener mOnKeyListener;
127 
128     private boolean mCreated = false;
129     @UnsupportedAppUsage
130     private boolean mShowing = false;
131     private boolean mCanceled = false;
132 
133     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
134     private final Handler mHandler = new Handler();
135 
136     private static final int DISMISS = 0x43;
137     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
138     private static final int CANCEL = 0x44;
139     private static final int SHOW = 0x45;
140 
141     @UnsupportedAppUsage
142     private final Handler mListenersHandler;
143 
144     private SearchEvent mSearchEvent;
145 
146     private ActionMode mActionMode;
147 
148     private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
149 
150     private final Runnable mDismissAction = this::dismissDialog;
151 
152     /**
153      * Creates a dialog window that uses the default dialog theme.
154      * <p>
155      * The supplied {@code context} is used to obtain the window manager and
156      * base theme used to present the dialog.
157      *
158      * @param context the context in which the dialog should run
159      * @see android.R.styleable#Theme_dialogTheme
160      */
Dialog(@onNull Context context)161     public Dialog(@NonNull Context context) {
162         this(context, 0, true);
163     }
164 
165     /**
166      * Creates a dialog window that uses a custom dialog style.
167      * <p>
168      * The supplied {@code context} is used to obtain the window manager and
169      * base theme used to present the dialog.
170      * <p>
171      * The supplied {@code theme} is applied on top of the context's theme. See
172      * <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
173      * Style and Theme Resources</a> for more information about defining and
174      * using styles.
175      *
176      * @param context the context in which the dialog should run
177      * @param themeResId a style resource describing the theme to use for the
178      *              window, or {@code 0} to use the default dialog theme
179      */
Dialog(@onNull Context context, @StyleRes int themeResId)180     public Dialog(@NonNull Context context, @StyleRes int themeResId) {
181         this(context, themeResId, true);
182     }
183 
Dialog(@onNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper)184     Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
185         if (createContextThemeWrapper) {
186             if (themeResId == Resources.ID_NULL) {
187                 final TypedValue outValue = new TypedValue();
188                 context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
189                 themeResId = outValue.resourceId;
190             }
191             mContext = new ContextThemeWrapper(context, themeResId);
192         } else {
193             mContext = context;
194         }
195 
196         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
197 
198         final Window w = new PhoneWindow(mContext);
199         mWindow = w;
200         w.setCallback(this);
201         w.setOnWindowDismissedCallback(this);
202         w.setOnWindowSwipeDismissedCallback(() -> {
203             if (mCancelable) {
204                 cancel();
205             }
206         });
207         w.setWindowManager(mWindowManager, null, null);
208         w.setGravity(Gravity.CENTER);
209 
210         mListenersHandler = new ListenersHandler(this);
211     }
212 
213     /**
214      * @deprecated
215      * @hide
216      */
217     @Deprecated
Dialog(@onNull Context context, boolean cancelable, @Nullable Message cancelCallback)218     protected Dialog(@NonNull Context context, boolean cancelable,
219             @Nullable Message cancelCallback) {
220         this(context);
221         mCancelable = cancelable;
222         updateWindowForCancelable();
223         mCancelMessage = cancelCallback;
224     }
225 
Dialog(@onNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener)226     protected Dialog(@NonNull Context context, boolean cancelable,
227             @Nullable OnCancelListener cancelListener) {
228         this(context);
229         mCancelable = cancelable;
230         updateWindowForCancelable();
231         setOnCancelListener(cancelListener);
232     }
233 
234     /**
235      * Retrieve the Context this Dialog is running in.
236      *
237      * @return Context The Context used by the Dialog.
238      */
getContext()239     public final @NonNull Context getContext() {
240         return mContext;
241     }
242 
243     /**
244      * Retrieve the {@link ActionBar} attached to this dialog, if present.
245      *
246      * @return The ActionBar attached to the dialog or null if no ActionBar is present.
247      */
getActionBar()248     public @Nullable ActionBar getActionBar() {
249         return mActionBar;
250     }
251 
252     /**
253      * Sets the Activity that owns this dialog. An example use: This Dialog will
254      * use the suggested volume control stream of the Activity.
255      *
256      * @param activity The Activity that owns this dialog.
257      */
setOwnerActivity(@onNull Activity activity)258     public final void setOwnerActivity(@NonNull Activity activity) {
259         mOwnerActivity = activity;
260 
261         getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream());
262     }
263 
264     /**
265      * Returns the Activity that owns this Dialog. For example, if
266      * {@link Activity#showDialog(int)} is used to show this Dialog, that
267      * Activity will be the owner (by default). Depending on how this dialog was
268      * created, this may return null.
269      *
270      * @return The Activity that owns this Dialog.
271      */
getOwnerActivity()272     public final @Nullable Activity getOwnerActivity() {
273         return mOwnerActivity;
274     }
275 
276     /**
277      * @return Whether the dialog is currently showing.
278      */
isShowing()279     public boolean isShowing() {
280         return mDecor == null ? false : mDecor.getVisibility() == View.VISIBLE;
281     }
282 
283     /**
284      * Forces immediate creation of the dialog.
285      * <p>
286      * Note that you should not override this method to perform dialog creation.
287      * Rather, override {@link #onCreate(Bundle)}.
288      */
create()289     public void create() {
290         if (!mCreated) {
291             dispatchOnCreate(null);
292         }
293     }
294 
295     /**
296      * Start the dialog and display it on screen.  The window is placed in the
297      * application layer and opaque.  Note that you should not override this
298      * method to do initialization when the dialog is shown, instead implement
299      * that in {@link #onStart}.
300      */
show()301     public void show() {
302         if (mShowing) {
303             if (mDecor != null) {
304                 if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
305                     mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
306                 }
307                 mDecor.setVisibility(View.VISIBLE);
308             }
309             return;
310         }
311 
312         mCanceled = false;
313 
314         if (!mCreated) {
315             dispatchOnCreate(null);
316         } else {
317             // Fill the DecorView in on any configuration changes that
318             // may have occured while it was removed from the WindowManager.
319             final Configuration config = mContext.getResources().getConfiguration();
320             mWindow.getDecorView().dispatchConfigurationChanged(config);
321         }
322 
323         onStart();
324         mDecor = mWindow.getDecorView();
325 
326         if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
327             final ApplicationInfo info = mContext.getApplicationInfo();
328             mWindow.setDefaultIcon(info.icon);
329             mWindow.setDefaultLogo(info.logo);
330             mActionBar = new WindowDecorActionBar(this);
331         }
332 
333         WindowManager.LayoutParams l = mWindow.getAttributes();
334         boolean restoreSoftInputMode = false;
335         if ((l.softInputMode
336                 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
337             l.softInputMode |=
338                     WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
339             restoreSoftInputMode = true;
340         }
341 
342         mWindowManager.addView(mDecor, l);
343         if (restoreSoftInputMode) {
344             l.softInputMode &=
345                     ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
346         }
347 
348         mShowing = true;
349 
350         sendShowMessage();
351     }
352 
353     /**
354      * Hide the dialog, but do not dismiss it.
355      */
hide()356     public void hide() {
357         if (mDecor != null) {
358             mDecor.setVisibility(View.GONE);
359         }
360     }
361 
362     /**
363      * Dismiss this dialog, removing it from the screen. This method can be
364      * invoked safely from any thread.  Note that you should not override this
365      * method to do cleanup when the dialog is dismissed, instead implement
366      * that in {@link #onStop}.
367      */
368     @Override
dismiss()369     public void dismiss() {
370         if (Looper.myLooper() == mHandler.getLooper()) {
371             dismissDialog();
372         } else {
373             mHandler.post(mDismissAction);
374         }
375     }
376 
377     @UnsupportedAppUsage
dismissDialog()378     void dismissDialog() {
379         if (mDecor == null || !mShowing) {
380             return;
381         }
382 
383         if (mWindow.isDestroyed()) {
384             Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
385             return;
386         }
387 
388         try {
389             mWindowManager.removeViewImmediate(mDecor);
390         } finally {
391             if (mActionMode != null) {
392                 mActionMode.finish();
393             }
394             mDecor = null;
395             mWindow.closeAllPanels();
396             onStop();
397             mShowing = false;
398 
399             sendDismissMessage();
400         }
401     }
402 
sendDismissMessage()403     private void sendDismissMessage() {
404         if (mDismissMessage != null) {
405             // Obtain a new message so this dialog can be re-used
406             Message.obtain(mDismissMessage).sendToTarget();
407         }
408     }
409 
sendShowMessage()410     private void sendShowMessage() {
411         if (mShowMessage != null) {
412             // Obtain a new message so this dialog can be re-used
413             Message.obtain(mShowMessage).sendToTarget();
414         }
415     }
416 
417     // internal method to make sure mCreated is set properly without requiring
418     // users to call through to super in onCreate
dispatchOnCreate(Bundle savedInstanceState)419     void dispatchOnCreate(Bundle savedInstanceState) {
420         if (!mCreated) {
421             onCreate(savedInstanceState);
422             mCreated = true;
423         }
424     }
425 
426     /**
427      * Similar to {@link Activity#onCreate}, you should initialize your dialog
428      * in this method, including calling {@link #setContentView}.
429      * @param savedInstanceState If this dialog is being reinitialized after a
430      *     the hosting activity was previously shut down, holds the result from
431      *     the most recent call to {@link #onSaveInstanceState}, or null if this
432      *     is the first time.
433      */
onCreate(Bundle savedInstanceState)434     protected void onCreate(Bundle savedInstanceState) {
435     }
436 
437     /**
438      * Called when the dialog is starting.
439      */
onStart()440     protected void onStart() {
441         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
442     }
443 
444     /**
445      * Called to tell you that you're stopping.
446      */
onStop()447     protected void onStop() {
448         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
449     }
450 
451     private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
452     private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
453 
454     /**
455      * Saves the state of the dialog into a bundle.
456      *
457      * The default implementation saves the state of its view hierarchy, so you'll
458      * likely want to call through to super if you override this to save additional
459      * state.
460      * @return A bundle with the state of the dialog.
461      */
onSaveInstanceState()462     public @NonNull Bundle onSaveInstanceState() {
463         Bundle bundle = new Bundle();
464         bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
465         if (mCreated) {
466             bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState());
467         }
468         return bundle;
469     }
470 
471     /**
472      * Restore the state of the dialog from a previously saved bundle.
473      *
474      * The default implementation restores the state of the dialog's view
475      * hierarchy that was saved in the default implementation of {@link #onSaveInstanceState()},
476      * so be sure to call through to super when overriding unless you want to
477      * do all restoring of state yourself.
478      * @param savedInstanceState The state of the dialog previously saved by
479      *     {@link #onSaveInstanceState()}.
480      */
onRestoreInstanceState(@onNull Bundle savedInstanceState)481     public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
482         final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
483         if (dialogHierarchyState == null) {
484             // dialog has never been shown, or onCreated, nothing to restore.
485             return;
486         }
487         dispatchOnCreate(savedInstanceState);
488         mWindow.restoreHierarchyState(dialogHierarchyState);
489         if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) {
490             show();
491         }
492     }
493 
494     /**
495      * Retrieve the current Window for the activity.  This can be used to
496      * directly access parts of the Window API that are not available
497      * through Activity/Screen.
498      *
499      * @return Window The current window, or null if the activity is not
500      *         visual.
501      */
getWindow()502     public @Nullable Window getWindow() {
503         return mWindow;
504     }
505 
506     /**
507      * Call {@link android.view.Window#getCurrentFocus} on the
508      * Window if this Activity to return the currently focused view.
509      *
510      * @return View The current View with focus or null.
511      *
512      * @see #getWindow
513      * @see android.view.Window#getCurrentFocus
514      */
getCurrentFocus()515     public @Nullable View getCurrentFocus() {
516         return mWindow != null ? mWindow.getCurrentFocus() : null;
517     }
518 
519     /**
520      * Finds the first descendant view with the given ID or {@code null} if the
521      * ID is invalid (< 0), there is no matching view in the hierarchy, or the
522      * dialog has not yet been fully created (for example, via {@link #show()}
523      * or {@link #create()}).
524      * <p>
525      * <strong>Note:</strong> In most cases -- depending on compiler support --
526      * the resulting view is automatically cast to the target class type. If
527      * the target class type is unconstrained, an explicit cast may be
528      * necessary.
529      *
530      * @param id the ID to search for
531      * @return a view with given ID if found, or {@code null} otherwise
532      * @see View#findViewById(int)
533      * @see Dialog#requireViewById(int)
534      */
535     @Nullable
findViewById(@dRes int id)536     public <T extends View> T findViewById(@IdRes int id) {
537         return mWindow.findViewById(id);
538     }
539 
540     /**
541      * Finds the first descendant view with the given ID or throws an IllegalArgumentException if
542      * the ID is invalid (< 0), there is no matching view in the hierarchy, or the dialog has not
543      * yet been fully created (for example, via {@link #show()} or {@link #create()}).
544      * <p>
545      * <strong>Note:</strong> In most cases -- depending on compiler support --
546      * the resulting view is automatically cast to the target class type. If
547      * the target class type is unconstrained, an explicit cast may be
548      * necessary.
549      *
550      * @param id the ID to search for
551      * @return a view with given ID
552      * @see View#requireViewById(int)
553      * @see Dialog#findViewById(int)
554      */
555     @NonNull
requireViewById(@dRes int id)556     public final <T extends View> T requireViewById(@IdRes int id) {
557         T view = findViewById(id);
558         if (view == null) {
559             throw new IllegalArgumentException("ID does not reference a View inside this Dialog");
560         }
561         return view;
562     }
563 
564     /**
565      * Set the screen content from a layout resource.  The resource will be
566      * inflated, adding all top-level views to the screen.
567      *
568      * @param layoutResID Resource ID to be inflated.
569      */
setContentView(@ayoutRes int layoutResID)570     public void setContentView(@LayoutRes int layoutResID) {
571         mWindow.setContentView(layoutResID);
572     }
573 
574     /**
575      * Set the screen content to an explicit view.  This view is placed
576      * directly into the screen's view hierarchy.  It can itself be a complex
577      * view hierarchy.
578      *
579      * @param view The desired content to display.
580      */
setContentView(@onNull View view)581     public void setContentView(@NonNull View view) {
582         mWindow.setContentView(view);
583     }
584 
585     /**
586      * Set the screen content to an explicit view.  This view is placed
587      * directly into the screen's view hierarchy.  It can itself be a complex
588      * view hierarchy.
589      *
590      * @param view The desired content to display.
591      * @param params Layout parameters for the view.
592      */
setContentView(@onNull View view, @Nullable ViewGroup.LayoutParams params)593     public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
594         mWindow.setContentView(view, params);
595     }
596 
597     /**
598      * Add an additional content view to the screen.  Added after any existing
599      * ones in the screen -- existing views are NOT removed.
600      *
601      * @param view The desired content to display.
602      * @param params Layout parameters for the view.
603      */
addContentView(@onNull View view, @Nullable ViewGroup.LayoutParams params)604     public void addContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
605         mWindow.addContentView(view, params);
606     }
607 
608     /**
609      * Set the title text for this dialog's window.
610      *
611      * @param title The new text to display in the title.
612      */
setTitle(@ullable CharSequence title)613     public void setTitle(@Nullable CharSequence title) {
614         mWindow.setTitle(title);
615         mWindow.getAttributes().setTitle(title);
616     }
617 
618     /**
619      * Set the title text for this dialog's window. The text is retrieved
620      * from the resources with the supplied identifier.
621      *
622      * @param titleId the title's text resource identifier
623      */
setTitle(@tringRes int titleId)624     public void setTitle(@StringRes int titleId) {
625         setTitle(mContext.getText(titleId));
626     }
627 
628     /**
629      * A key was pressed down.
630      * <p>
631      * If the focused view didn't want this event, this method is called.
632      * <p>
633      * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
634      * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
635      * KEYCODE_ESCAPE} to later handle them in {@link #onKeyUp}.
636      *
637      * @see #onKeyUp
638      * @see android.view.KeyEvent
639      */
640     @Override
onKeyDown(int keyCode, @NonNull KeyEvent event)641     public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
642         if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
643             event.startTracking();
644             return true;
645         }
646 
647         return false;
648     }
649 
650     /**
651      * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
652      * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
653      * the event).
654      */
655     @Override
onKeyLongPress(int keyCode, @NonNull KeyEvent event)656     public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
657         return false;
658     }
659 
660     /**
661      * A key was released.
662      * <p>
663      * Default implementation consumes {@link KeyEvent#KEYCODE_BACK KEYCODE_BACK}
664      * and, as of {@link android.os.Build.VERSION_CODES#P P}, {@link KeyEvent#KEYCODE_ESCAPE
665      * KEYCODE_ESCAPE} to close the dialog.
666      *
667      * @see #onKeyDown
668      * @see android.view.KeyEvent
669      */
670     @Override
onKeyUp(int keyCode, @NonNull KeyEvent event)671     public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
672         if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
673                 && event.isTracking()
674                 && !event.isCanceled()) {
675             onBackPressed();
676             return true;
677         }
678         return false;
679     }
680 
681     /**
682      * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
683      * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
684      * the event).
685      */
686     @Override
onKeyMultiple(int keyCode, int repeatCount, @NonNull KeyEvent event)687     public boolean onKeyMultiple(int keyCode, int repeatCount, @NonNull KeyEvent event) {
688         return false;
689     }
690 
691     /**
692      * Called when the dialog has detected the user's press of the back
693      * key.  The default implementation simply cancels the dialog (only if
694      * it is cancelable), but you can override this to do whatever you want.
695      */
onBackPressed()696     public void onBackPressed() {
697         if (mCancelable) {
698             cancel();
699         }
700     }
701 
702     /**
703      * Called when a key shortcut event is not handled by any of the views in the Dialog.
704      * Override this method to implement global key shortcuts for the Dialog.
705      * Key shortcuts can also be implemented by setting the
706      * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
707      *
708      * @param keyCode The value in event.getKeyCode().
709      * @param event Description of the key event.
710      * @return True if the key shortcut was handled.
711      */
onKeyShortcut(int keyCode, @NonNull KeyEvent event)712     public boolean onKeyShortcut(int keyCode, @NonNull KeyEvent event) {
713         return false;
714     }
715 
716     /**
717      * Called when a touch screen event was not handled by any of the views
718      * under it. This is most useful to process touch events that happen outside
719      * of your window bounds, where there is no view to receive it.
720      *
721      * @param event The touch screen event being processed.
722      * @return Return true if you have consumed the event, false if you haven't.
723      *         The default implementation will cancel the dialog when a touch
724      *         happens outside of the window bounds.
725      */
onTouchEvent(@onNull MotionEvent event)726     public boolean onTouchEvent(@NonNull MotionEvent event) {
727         if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) {
728             cancel();
729             return true;
730         }
731 
732         return false;
733     }
734 
735     /**
736      * Called when the trackball was moved and not handled by any of the
737      * views inside of the activity.  So, for example, if the trackball moves
738      * while focus is on a button, you will receive a call here because
739      * buttons do not normally do anything with trackball events.  The call
740      * here happens <em>before</em> trackball movements are converted to
741      * DPAD key events, which then get sent back to the view hierarchy, and
742      * will be processed at the point for things like focus navigation.
743      *
744      * @param event The trackball event being processed.
745      *
746      * @return Return true if you have consumed the event, false if you haven't.
747      * The default implementation always returns false.
748      */
onTrackballEvent(@onNull MotionEvent event)749     public boolean onTrackballEvent(@NonNull MotionEvent event) {
750         return false;
751     }
752 
753     /**
754      * Called when a generic motion event was not handled by any of the
755      * views inside of the dialog.
756      * <p>
757      * Generic motion events describe joystick movements, mouse hovers, track pad
758      * touches, scroll wheel movements and other input events.  The
759      * {@link MotionEvent#getSource() source} of the motion event specifies
760      * the class of input that was received.  Implementations of this method
761      * must examine the bits in the source before processing the event.
762      * The following code example shows how this is done.
763      * </p><p>
764      * Generic motion events with source class
765      * {@link android.view.InputDevice#SOURCE_CLASS_POINTER}
766      * are delivered to the view under the pointer.  All other generic motion events are
767      * delivered to the focused view.
768      * </p><p>
769      * See {@link View#onGenericMotionEvent(MotionEvent)} for an example of how to
770      * handle this event.
771      * </p>
772      *
773      * @param event The generic motion event being processed.
774      *
775      * @return Return true if you have consumed the event, false if you haven't.
776      * The default implementation always returns false.
777      */
onGenericMotionEvent(@onNull MotionEvent event)778     public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
779         return false;
780     }
781 
782     @Override
onWindowAttributesChanged(WindowManager.LayoutParams params)783     public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
784         if (mDecor != null) {
785             mWindowManager.updateViewLayout(mDecor, params);
786         }
787     }
788 
789     @Override
onContentChanged()790     public void onContentChanged() {
791     }
792 
793     @Override
onWindowFocusChanged(boolean hasFocus)794     public void onWindowFocusChanged(boolean hasFocus) {
795     }
796 
797     @Override
onAttachedToWindow()798     public void onAttachedToWindow() {
799     }
800 
801     @Override
onDetachedFromWindow()802     public void onDetachedFromWindow() {
803     }
804 
805     /** @hide */
806     @Override
onWindowDismissed(boolean finishTask, boolean suppressWindowTransition)807     public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
808         dismiss();
809     }
810 
811     /**
812      * Called to process key events.  You can override this to intercept all
813      * key events before they are dispatched to the window.  Be sure to call
814      * this implementation for key events that should be handled normally.
815      *
816      * @param event The key event.
817      *
818      * @return boolean Return true if this event was consumed.
819      */
820     @Override
dispatchKeyEvent(@onNull KeyEvent event)821     public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
822         if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) {
823             return true;
824         }
825         if (mWindow.superDispatchKeyEvent(event)) {
826             return true;
827         }
828         return event.dispatch(this, mDecor != null
829                 ? mDecor.getKeyDispatcherState() : null, this);
830     }
831 
832     /**
833      * Called to process a key shortcut event.
834      * You can override this to intercept all key shortcut events before they are
835      * dispatched to the window.  Be sure to call this implementation for key shortcut
836      * events that should be handled normally.
837      *
838      * @param event The key shortcut event.
839      * @return True if this event was consumed.
840      */
841     @Override
dispatchKeyShortcutEvent(@onNull KeyEvent event)842     public boolean dispatchKeyShortcutEvent(@NonNull KeyEvent event) {
843         if (mWindow.superDispatchKeyShortcutEvent(event)) {
844             return true;
845         }
846         return onKeyShortcut(event.getKeyCode(), event);
847     }
848 
849     /**
850      * Called to process touch screen events.  You can override this to
851      * intercept all touch screen events before they are dispatched to the
852      * window.  Be sure to call this implementation for touch screen events
853      * that should be handled normally.
854      *
855      * @param ev The touch screen event.
856      *
857      * @return boolean Return true if this event was consumed.
858      */
859     @Override
dispatchTouchEvent(@onNull MotionEvent ev)860     public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
861         if (mWindow.superDispatchTouchEvent(ev)) {
862             return true;
863         }
864         return onTouchEvent(ev);
865     }
866 
867     /**
868      * Called to process trackball events.  You can override this to
869      * intercept all trackball events before they are dispatched to the
870      * window.  Be sure to call this implementation for trackball events
871      * that should be handled normally.
872      *
873      * @param ev The trackball event.
874      *
875      * @return boolean Return true if this event was consumed.
876      */
877     @Override
dispatchTrackballEvent(@onNull MotionEvent ev)878     public boolean dispatchTrackballEvent(@NonNull MotionEvent ev) {
879         if (mWindow.superDispatchTrackballEvent(ev)) {
880             return true;
881         }
882         return onTrackballEvent(ev);
883     }
884 
885     /**
886      * Called to process generic motion events.  You can override this to
887      * intercept all generic motion events before they are dispatched to the
888      * window.  Be sure to call this implementation for generic motion events
889      * that should be handled normally.
890      *
891      * @param ev The generic motion event.
892      *
893      * @return boolean Return true if this event was consumed.
894      */
895     @Override
dispatchGenericMotionEvent(@onNull MotionEvent ev)896     public boolean dispatchGenericMotionEvent(@NonNull MotionEvent ev) {
897         if (mWindow.superDispatchGenericMotionEvent(ev)) {
898             return true;
899         }
900         return onGenericMotionEvent(ev);
901     }
902 
903     @Override
dispatchPopulateAccessibilityEvent(@onNull AccessibilityEvent event)904     public boolean dispatchPopulateAccessibilityEvent(@NonNull AccessibilityEvent event) {
905         event.setClassName(getClass().getName());
906         event.setPackageName(mContext.getPackageName());
907 
908         LayoutParams params = getWindow().getAttributes();
909         boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
910             (params.height == LayoutParams.MATCH_PARENT);
911         event.setFullScreen(isFullScreen);
912 
913         return false;
914     }
915 
916     /**
917      * @see Activity#onCreatePanelView(int)
918      */
919     @Override
onCreatePanelView(int featureId)920     public View onCreatePanelView(int featureId) {
921         return null;
922     }
923 
924     /**
925      * @see Activity#onCreatePanelMenu(int, Menu)
926      */
927     @Override
onCreatePanelMenu(int featureId, @NonNull Menu menu)928     public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
929         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
930             return onCreateOptionsMenu(menu);
931         }
932 
933         return false;
934     }
935 
936     /**
937      * @see Activity#onPreparePanel(int, View, Menu)
938      */
939     @Override
onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu)940     public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
941         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
942             return onPrepareOptionsMenu(menu) && menu.hasVisibleItems();
943         }
944         return true;
945     }
946 
947     /**
948      * @see Activity#onMenuOpened(int, Menu)
949      */
950     @Override
onMenuOpened(int featureId, @NonNull Menu menu)951     public boolean onMenuOpened(int featureId, @NonNull Menu menu) {
952         if (featureId == Window.FEATURE_ACTION_BAR) {
953             mActionBar.dispatchMenuVisibilityChanged(true);
954         }
955         return true;
956     }
957 
958     /**
959      * @see Activity#onMenuItemSelected(int, MenuItem)
960      */
961     @Override
onMenuItemSelected(int featureId, @NonNull MenuItem item)962     public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
963         return false;
964     }
965 
966     /**
967      * @see Activity#onPanelClosed(int, Menu)
968      */
969     @Override
onPanelClosed(int featureId, @NonNull Menu menu)970     public void onPanelClosed(int featureId, @NonNull Menu menu) {
971         if (featureId == Window.FEATURE_ACTION_BAR) {
972             mActionBar.dispatchMenuVisibilityChanged(false);
973         }
974     }
975 
976     /**
977      * It is usually safe to proxy this call to the owner activity's
978      * {@link Activity#onCreateOptionsMenu(Menu)} if the client desires the same
979      * menu for this Dialog.
980      *
981      * @see Activity#onCreateOptionsMenu(Menu)
982      * @see #getOwnerActivity()
983      */
onCreateOptionsMenu(@onNull Menu menu)984     public boolean onCreateOptionsMenu(@NonNull Menu menu) {
985         return true;
986     }
987 
988     /**
989      * It is usually safe to proxy this call to the owner activity's
990      * {@link Activity#onPrepareOptionsMenu(Menu)} if the client desires the
991      * same menu for this Dialog.
992      *
993      * @see Activity#onPrepareOptionsMenu(Menu)
994      * @see #getOwnerActivity()
995      */
onPrepareOptionsMenu(@onNull Menu menu)996     public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
997         return true;
998     }
999 
1000     /**
1001      * @see Activity#onOptionsItemSelected(MenuItem)
1002      */
onOptionsItemSelected(@onNull MenuItem item)1003     public boolean onOptionsItemSelected(@NonNull MenuItem item) {
1004         return false;
1005     }
1006 
1007     /**
1008      * @see Activity#onOptionsMenuClosed(Menu)
1009      */
onOptionsMenuClosed(@onNull Menu menu)1010     public void onOptionsMenuClosed(@NonNull Menu menu) {
1011     }
1012 
1013     /**
1014      * @see Activity#openOptionsMenu()
1015      */
openOptionsMenu()1016     public void openOptionsMenu() {
1017         if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
1018             mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
1019         }
1020     }
1021 
1022     /**
1023      * @see Activity#closeOptionsMenu()
1024      */
closeOptionsMenu()1025     public void closeOptionsMenu() {
1026         if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
1027             mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
1028         }
1029     }
1030 
1031     /**
1032      * @see Activity#invalidateOptionsMenu()
1033      */
invalidateOptionsMenu()1034     public void invalidateOptionsMenu() {
1035         if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
1036             mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
1037         }
1038     }
1039 
1040     /**
1041      * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
1042      */
1043     @Override
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)1044     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
1045     }
1046 
1047     /**
1048      * @see Activity#registerForContextMenu(View)
1049      */
registerForContextMenu(@onNull View view)1050     public void registerForContextMenu(@NonNull View view) {
1051         view.setOnCreateContextMenuListener(this);
1052     }
1053 
1054     /**
1055      * @see Activity#unregisterForContextMenu(View)
1056      */
unregisterForContextMenu(@onNull View view)1057     public void unregisterForContextMenu(@NonNull View view) {
1058         view.setOnCreateContextMenuListener(null);
1059     }
1060 
1061     /**
1062      * @see Activity#openContextMenu(View)
1063      */
openContextMenu(@onNull View view)1064     public void openContextMenu(@NonNull View view) {
1065         view.showContextMenu();
1066     }
1067 
1068     /**
1069      * @see Activity#onContextItemSelected(MenuItem)
1070      */
onContextItemSelected(@onNull MenuItem item)1071     public boolean onContextItemSelected(@NonNull MenuItem item) {
1072         return false;
1073     }
1074 
1075     /**
1076      * @see Activity#onContextMenuClosed(Menu)
1077      */
onContextMenuClosed(@onNull Menu menu)1078     public void onContextMenuClosed(@NonNull Menu menu) {
1079     }
1080 
1081     /**
1082      * This hook is called when the user signals the desire to start a search.
1083      */
1084     @Override
onSearchRequested(@onNull SearchEvent searchEvent)1085     public boolean onSearchRequested(@NonNull SearchEvent searchEvent) {
1086         mSearchEvent = searchEvent;
1087         return onSearchRequested();
1088     }
1089 
1090     /**
1091      * This hook is called when the user signals the desire to start a search.
1092      */
1093     @Override
onSearchRequested()1094     public boolean onSearchRequested() {
1095         final SearchManager searchManager = (SearchManager) mContext
1096                 .getSystemService(Context.SEARCH_SERVICE);
1097 
1098         // associate search with owner activity
1099         final ComponentName appName = getAssociatedActivity();
1100         if (appName != null && searchManager.getSearchableInfo(appName) != null) {
1101             searchManager.startSearch(null, false, appName, null, false);
1102             dismiss();
1103             return true;
1104         } else {
1105             return false;
1106         }
1107     }
1108 
1109     /**
1110      * During the onSearchRequested() callbacks, this function will return the
1111      * {@link SearchEvent} that triggered the callback, if it exists.
1112      *
1113      * @return SearchEvent The SearchEvent that triggered the {@link
1114      *                    #onSearchRequested} callback.
1115      */
getSearchEvent()1116     public final @Nullable SearchEvent getSearchEvent() {
1117         return mSearchEvent;
1118     }
1119 
1120     @Override
onWindowStartingActionMode(ActionMode.Callback callback)1121     public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
1122         if (mActionBar != null && mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) {
1123             return mActionBar.startActionMode(callback);
1124         }
1125         return null;
1126     }
1127 
1128     @Override
onWindowStartingActionMode(ActionMode.Callback callback, int type)1129     public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
1130         try {
1131             mActionModeTypeStarting = type;
1132             return onWindowStartingActionMode(callback);
1133         } finally {
1134             mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
1135         }
1136     }
1137 
1138     /**
1139      * {@inheritDoc}
1140      *
1141      * Note that if you override this method you should always call through
1142      * to the superclass implementation by calling super.onActionModeStarted(mode).
1143      */
1144     @Override
1145     @CallSuper
onActionModeStarted(ActionMode mode)1146     public void onActionModeStarted(ActionMode mode) {
1147         mActionMode = mode;
1148     }
1149 
1150     /**
1151      * {@inheritDoc}
1152      *
1153      * Note that if you override this method you should always call through
1154      * to the superclass implementation by calling super.onActionModeFinished(mode).
1155      */
1156     @Override
1157     @CallSuper
onActionModeFinished(ActionMode mode)1158     public void onActionModeFinished(ActionMode mode) {
1159         if (mode == mActionMode) {
1160             mActionMode = null;
1161         }
1162     }
1163 
1164     /**
1165      * @return The activity associated with this dialog, or null if there is no associated activity.
1166      */
getAssociatedActivity()1167     private ComponentName getAssociatedActivity() {
1168         Activity activity = mOwnerActivity;
1169         Context context = getContext();
1170         while (activity == null && context != null) {
1171             if (context instanceof Activity) {
1172                 activity = (Activity) context;  // found it!
1173             } else {
1174                 context = (context instanceof ContextWrapper) ?
1175                         ((ContextWrapper) context).getBaseContext() : // unwrap one level
1176                         null;                                         // done
1177             }
1178         }
1179         return activity == null ? null : activity.getComponentName();
1180     }
1181 
1182 
1183     /**
1184      * Request that key events come to this dialog. Use this if your
1185      * dialog has no views with focus, but the dialog still wants
1186      * a chance to process key events.
1187      *
1188      * @param get true if the dialog should receive key events, false otherwise
1189      * @see android.view.Window#takeKeyEvents
1190      */
takeKeyEvents(boolean get)1191     public void takeKeyEvents(boolean get) {
1192         mWindow.takeKeyEvents(get);
1193     }
1194 
1195     /**
1196      * Enable extended window features.  This is a convenience for calling
1197      * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
1198      *
1199      * @param featureId The desired feature as defined in
1200      *                  {@link android.view.Window}.
1201      * @return Returns true if the requested feature is supported and now
1202      *         enabled.
1203      *
1204      * @see android.view.Window#requestFeature
1205      */
requestWindowFeature(int featureId)1206     public final boolean requestWindowFeature(int featureId) {
1207         return getWindow().requestFeature(featureId);
1208     }
1209 
1210     /**
1211      * Convenience for calling
1212      * {@link android.view.Window#setFeatureDrawableResource}.
1213      */
setFeatureDrawableResource(int featureId, @DrawableRes int resId)1214     public final void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
1215         getWindow().setFeatureDrawableResource(featureId, resId);
1216     }
1217 
1218     /**
1219      * Convenience for calling
1220      * {@link android.view.Window#setFeatureDrawableUri}.
1221      */
setFeatureDrawableUri(int featureId, @Nullable Uri uri)1222     public final void setFeatureDrawableUri(int featureId, @Nullable Uri uri) {
1223         getWindow().setFeatureDrawableUri(featureId, uri);
1224     }
1225 
1226     /**
1227      * Convenience for calling
1228      * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
1229      */
setFeatureDrawable(int featureId, @Nullable Drawable drawable)1230     public final void setFeatureDrawable(int featureId, @Nullable Drawable drawable) {
1231         getWindow().setFeatureDrawable(featureId, drawable);
1232     }
1233 
1234     /**
1235      * Convenience for calling
1236      * {@link android.view.Window#setFeatureDrawableAlpha}.
1237      */
setFeatureDrawableAlpha(int featureId, int alpha)1238     public final void setFeatureDrawableAlpha(int featureId, int alpha) {
1239         getWindow().setFeatureDrawableAlpha(featureId, alpha);
1240     }
1241 
getLayoutInflater()1242     public @NonNull LayoutInflater getLayoutInflater() {
1243         return getWindow().getLayoutInflater();
1244     }
1245 
1246     /**
1247      * Sets whether this dialog is cancelable with the
1248      * {@link KeyEvent#KEYCODE_BACK BACK} key.
1249      */
setCancelable(boolean flag)1250     public void setCancelable(boolean flag) {
1251         mCancelable = flag;
1252         updateWindowForCancelable();
1253     }
1254 
1255     /**
1256      * Sets whether this dialog is canceled when touched outside the window's
1257      * bounds. If setting to true, the dialog is set to be cancelable if not
1258      * already set.
1259      *
1260      * @param cancel Whether the dialog should be canceled when touched outside
1261      *            the window.
1262      */
setCanceledOnTouchOutside(boolean cancel)1263     public void setCanceledOnTouchOutside(boolean cancel) {
1264         if (cancel && !mCancelable) {
1265             mCancelable = true;
1266             updateWindowForCancelable();
1267         }
1268 
1269         mWindow.setCloseOnTouchOutside(cancel);
1270     }
1271 
1272     /**
1273      * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
1274      * also call your {@link DialogInterface.OnCancelListener} (if registered).
1275      */
1276     @Override
cancel()1277     public void cancel() {
1278         if (!mCanceled && mCancelMessage != null) {
1279             mCanceled = true;
1280             // Obtain a new message so this dialog can be re-used
1281             Message.obtain(mCancelMessage).sendToTarget();
1282         }
1283         dismiss();
1284     }
1285 
1286     /**
1287      * Set a listener to be invoked when the dialog is canceled.
1288      *
1289      * <p>This will only be invoked when the dialog is canceled.
1290      * Cancel events alone will not capture all ways that
1291      * the dialog might be dismissed. If the creator needs
1292      * to know when a dialog is dismissed in general, use
1293      * {@link #setOnDismissListener}.</p>
1294      *
1295      * @param listener The {@link DialogInterface.OnCancelListener} to use.
1296      */
setOnCancelListener(@ullable OnCancelListener listener)1297     public void setOnCancelListener(@Nullable OnCancelListener listener) {
1298         if (mCancelAndDismissTaken != null) {
1299             throw new IllegalStateException(
1300                     "OnCancelListener is already taken by "
1301                     + mCancelAndDismissTaken + " and can not be replaced.");
1302         }
1303         if (listener != null) {
1304             mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
1305         } else {
1306             mCancelMessage = null;
1307         }
1308     }
1309 
1310     /**
1311      * Set a message to be sent when the dialog is canceled.
1312      * @param msg The msg to send when the dialog is canceled.
1313      * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener)
1314      */
setCancelMessage(@ullable Message msg)1315     public void setCancelMessage(@Nullable Message msg) {
1316         mCancelMessage = msg;
1317     }
1318 
1319     /**
1320      * Set a listener to be invoked when the dialog is dismissed.
1321      * @param listener The {@link DialogInterface.OnDismissListener} to use.
1322      */
setOnDismissListener(@ullable OnDismissListener listener)1323     public void setOnDismissListener(@Nullable OnDismissListener listener) {
1324         if (mCancelAndDismissTaken != null) {
1325             throw new IllegalStateException(
1326                     "OnDismissListener is already taken by "
1327                     + mCancelAndDismissTaken + " and can not be replaced.");
1328         }
1329         if (listener != null) {
1330             mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
1331         } else {
1332             mDismissMessage = null;
1333         }
1334     }
1335 
1336     /**
1337      * Sets a listener to be invoked when the dialog is shown.
1338      * @param listener The {@link DialogInterface.OnShowListener} to use.
1339      */
setOnShowListener(@ullable OnShowListener listener)1340     public void setOnShowListener(@Nullable OnShowListener listener) {
1341         if (listener != null) {
1342             mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
1343         } else {
1344             mShowMessage = null;
1345         }
1346     }
1347 
1348     /**
1349      * Set a message to be sent when the dialog is dismissed.
1350      * @param msg The msg to send when the dialog is dismissed.
1351      */
setDismissMessage(@ullable Message msg)1352     public void setDismissMessage(@Nullable Message msg) {
1353         mDismissMessage = msg;
1354     }
1355 
1356     /** @hide */
takeCancelAndDismissListeners(@ullable String msg, @Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss)1357     public boolean takeCancelAndDismissListeners(@Nullable String msg,
1358             @Nullable OnCancelListener cancel, @Nullable OnDismissListener dismiss) {
1359         if (mCancelAndDismissTaken != null) {
1360             mCancelAndDismissTaken = null;
1361         } else if (mCancelMessage != null || mDismissMessage != null) {
1362             return false;
1363         }
1364 
1365         setOnCancelListener(cancel);
1366         setOnDismissListener(dismiss);
1367         mCancelAndDismissTaken = msg;
1368 
1369         return true;
1370     }
1371 
1372     /**
1373      * By default, this will use the owner Activity's suggested stream type.
1374      *
1375      * @see Activity#setVolumeControlStream(int)
1376      * @see #setOwnerActivity(Activity)
1377      */
setVolumeControlStream(int streamType)1378     public final void setVolumeControlStream(int streamType) {
1379         getWindow().setVolumeControlStream(streamType);
1380     }
1381 
1382     /**
1383      * @see Activity#getVolumeControlStream()
1384      */
getVolumeControlStream()1385     public final int getVolumeControlStream() {
1386         return getWindow().getVolumeControlStream();
1387     }
1388 
1389     /**
1390      * Sets the callback that will be called if a key is dispatched to the dialog.
1391      */
setOnKeyListener(@ullable OnKeyListener onKeyListener)1392     public void setOnKeyListener(@Nullable OnKeyListener onKeyListener) {
1393         mOnKeyListener = onKeyListener;
1394     }
1395 
1396     private static final class ListenersHandler extends Handler {
1397         private final WeakReference<DialogInterface> mDialog;
1398 
ListenersHandler(Dialog dialog)1399         public ListenersHandler(Dialog dialog) {
1400             mDialog = new WeakReference<>(dialog);
1401         }
1402 
1403         @Override
handleMessage(Message msg)1404         public void handleMessage(Message msg) {
1405             switch (msg.what) {
1406                 case DISMISS:
1407                     ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
1408                     break;
1409                 case CANCEL:
1410                     ((OnCancelListener) msg.obj).onCancel(mDialog.get());
1411                     break;
1412                 case SHOW:
1413                     ((OnShowListener) msg.obj).onShow(mDialog.get());
1414                     break;
1415             }
1416         }
1417     }
1418 
updateWindowForCancelable()1419     private void updateWindowForCancelable() {
1420         mWindow.setCloseOnSwipeEnabled(mCancelable);
1421     }
1422 }
1423