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