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