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