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