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