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