Dialog.java revision 6de4aed1c67263269f83f579ec5b06263d173ef3
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        if (!mCreated) {
296            onCreate(savedInstanceState);
297            mCreated = true;
298        }
299    }
300
301    /**
302     * Similar to {@link Activity#onCreate}, you should initialized your dialog
303     * in this method, including calling {@link #setContentView}.
304     * @param savedInstanceState If this dialog is being reinitalized after a
305     *     the hosting activity was previously shut down, holds the result from
306     *     the most recent call to {@link #onSaveInstanceState}, or null if this
307     *     is the first time.
308     */
309    protected void onCreate(Bundle savedInstanceState) {
310    }
311
312    /**
313     * Called when the dialog is starting.
314     */
315    protected void onStart() {
316    }
317
318    /**
319     * Called to tell you that you're stopping.
320     */
321    protected void onStop() {
322    }
323
324    private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
325    private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy";
326
327    /**
328     * Saves the state of the dialog into a bundle.
329     *
330     * The default implementation saves the state of its view hierarchy, so you'll
331     * likely want to call through to super if you override this to save additional
332     * state.
333     * @return A bundle with the state of the dialog.
334     */
335    public Bundle onSaveInstanceState() {
336        Bundle bundle = new Bundle();
337        bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing);
338        if (mCreated) {
339            bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState());
340        }
341        return bundle;
342    }
343
344    /**
345     * Restore the state of the dialog from a previously saved bundle.
346     *
347     * The default implementation restores the state of the dialog's view
348     * hierarchy that was saved in the default implementation of {@link #onSaveInstanceState()},
349     * so be sure to call through to super when overriding unless you want to
350     * do all restoring of state yourself.
351     * @param savedInstanceState The state of the dialog previously saved by
352     *     {@link #onSaveInstanceState()}.
353     */
354    public void onRestoreInstanceState(Bundle savedInstanceState) {
355        final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG);
356        if (dialogHierarchyState == null) {
357            // dialog has never been shown, or onCreated, nothing to restore.
358            return;
359        }
360        dispatchOnCreate(savedInstanceState);
361        mWindow.restoreHierarchyState(dialogHierarchyState);
362        if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) {
363            show();
364        }
365    }
366
367    /**
368     * Retrieve the current Window for the activity.  This can be used to
369     * directly access parts of the Window API that are not available
370     * through Activity/Screen.
371     *
372     * @return Window The current window, or null if the activity is not
373     *         visual.
374     */
375    public Window getWindow() {
376        return mWindow;
377    }
378
379    /**
380     * Call {@link android.view.Window#getCurrentFocus} on the
381     * Window if this Activity to return the currently focused view.
382     *
383     * @return View The current View with focus or null.
384     *
385     * @see #getWindow
386     * @see android.view.Window#getCurrentFocus
387     */
388    public View getCurrentFocus() {
389        return mWindow != null ? mWindow.getCurrentFocus() : null;
390    }
391
392    /**
393     * Finds a view that was identified by the id attribute from the XML that
394     * was processed in {@link #onStart}.
395     *
396     * @param id the identifier of the view to find
397     * @return The view if found or null otherwise.
398     */
399    public View findViewById(int id) {
400        return mWindow.findViewById(id);
401    }
402
403    /**
404     * Set the screen content from a layout resource.  The resource will be
405     * inflated, adding all top-level views to the screen.
406     *
407     * @param layoutResID Resource ID to be inflated.
408     */
409    public void setContentView(int layoutResID) {
410        mWindow.setContentView(layoutResID);
411    }
412
413    /**
414     * Set the screen content to an explicit view.  This view is placed
415     * directly into the screen's view hierarchy.  It can itself be a complex
416     * view hierarhcy.
417     *
418     * @param view The desired content to display.
419     */
420    public void setContentView(View view) {
421        mWindow.setContentView(view);
422    }
423
424    /**
425     * Set the screen content to an explicit view.  This view is placed
426     * directly into the screen's view hierarchy.  It can itself be a complex
427     * view hierarhcy.
428     *
429     * @param view The desired content to display.
430     * @param params Layout parameters for the view.
431     */
432    public void setContentView(View view, ViewGroup.LayoutParams params) {
433        mWindow.setContentView(view, params);
434    }
435
436    /**
437     * Add an additional content view to the screen.  Added after any existing
438     * ones in the screen -- existing views are NOT removed.
439     *
440     * @param view The desired content to display.
441     * @param params Layout parameters for the view.
442     */
443    public void addContentView(View view, ViewGroup.LayoutParams params) {
444        mWindow.addContentView(view, params);
445    }
446
447    /**
448     * Set the title text for this dialog's window.
449     *
450     * @param title The new text to display in the title.
451     */
452    public void setTitle(CharSequence title) {
453        mWindow.setTitle(title);
454        mWindow.getAttributes().setTitle(title);
455    }
456
457    /**
458     * Set the title text for this dialog's window. The text is retrieved
459     * from the resources with the supplied identifier.
460     *
461     * @param titleId the title's text resource identifier
462     */
463    public void setTitle(int titleId) {
464        setTitle(mContext.getText(titleId));
465    }
466
467    /**
468     * A key was pressed down.
469     *
470     * <p>If the focused view didn't want this event, this method is called.
471     *
472     * <p>The default implementation handles KEYCODE_BACK to close the
473     * dialog.
474     *
475     * @see #onKeyUp
476     * @see android.view.KeyEvent
477     */
478    public boolean onKeyDown(int keyCode, KeyEvent event) {
479        if (keyCode == KeyEvent.KEYCODE_BACK) {
480            if (mCancelable) {
481                cancel();
482            }
483            return true;
484        }
485
486        return false;
487    }
488
489    /**
490     * A key was released.
491     *
492     * @see #onKeyDown
493     * @see KeyEvent
494     */
495    public boolean onKeyUp(int keyCode, KeyEvent event) {
496        return false;
497    }
498
499    /**
500     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
501     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
502     * the event).
503     */
504    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
505        return false;
506    }
507
508    /**
509     * Called when a touch screen event was not handled by any of the views
510     * under it. This is most useful to process touch events that happen outside
511     * of your window bounds, where there is no view to receive it.
512     *
513     * @param event The touch screen event being processed.
514     * @return Return true if you have consumed the event, false if you haven't.
515     *         The default implementation will cancel the dialog when a touch
516     *         happens outside of the window bounds.
517     */
518    public boolean onTouchEvent(MotionEvent event) {
519        if (mCancelable && mCanceledOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
520                && isOutOfBounds(event)) {
521            cancel();
522            return true;
523        }
524
525        return false;
526    }
527
528    private boolean isOutOfBounds(MotionEvent event) {
529        final int x = (int) event.getX();
530        final int y = (int) event.getY();
531        final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
532        final View decorView = getWindow().getDecorView();
533        return (x < -slop) || (y < -slop)
534                || (x > (decorView.getWidth()+slop))
535                || (y > (decorView.getHeight()+slop));
536    }
537
538    /**
539     * Called when the trackball was moved and not handled by any of the
540     * views inside of the activity.  So, for example, if the trackball moves
541     * while focus is on a button, you will receive a call here because
542     * buttons do not normally do anything with trackball events.  The call
543     * here happens <em>before</em> trackball movements are converted to
544     * DPAD key events, which then get sent back to the view hierarchy, and
545     * will be processed at the point for things like focus navigation.
546     *
547     * @param event The trackball event being processed.
548     *
549     * @return Return true if you have consumed the event, false if you haven't.
550     * The default implementation always returns false.
551     */
552    public boolean onTrackballEvent(MotionEvent event) {
553        return false;
554    }
555
556    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
557        if (mDecor != null) {
558            mWindowManager.updateViewLayout(mDecor, params);
559        }
560    }
561
562    public void onContentChanged() {
563    }
564
565    public void onWindowFocusChanged(boolean hasFocus) {
566    }
567
568    /**
569     * Called to process key events.  You can override this to intercept all
570     * key events before they are dispatched to the window.  Be sure to call
571     * this implementation for key events that should be handled normally.
572     *
573     * @param event The key event.
574     *
575     * @return boolean Return true if this event was consumed.
576     */
577    public boolean dispatchKeyEvent(KeyEvent event) {
578        if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) {
579            return true;
580        }
581        if (mWindow.superDispatchKeyEvent(event)) {
582            return true;
583        }
584        return event.dispatch(this);
585    }
586
587    /**
588     * Called to process touch screen events.  You can override this to
589     * intercept all touch screen events before they are dispatched to the
590     * window.  Be sure to call this implementation for touch screen events
591     * that should be handled normally.
592     *
593     * @param ev The touch screen event.
594     *
595     * @return boolean Return true if this event was consumed.
596     */
597    public boolean dispatchTouchEvent(MotionEvent ev) {
598        if (mWindow.superDispatchTouchEvent(ev)) {
599            return true;
600        }
601        return onTouchEvent(ev);
602    }
603
604    /**
605     * Called to process trackball events.  You can override this to
606     * intercept all trackball events before they are dispatched to the
607     * window.  Be sure to call this implementation for trackball events
608     * that should be handled normally.
609     *
610     * @param ev The trackball event.
611     *
612     * @return boolean Return true if this event was consumed.
613     */
614    public boolean dispatchTrackballEvent(MotionEvent ev) {
615        if (mWindow.superDispatchTrackballEvent(ev)) {
616            return true;
617        }
618        return onTrackballEvent(ev);
619    }
620
621    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
622        event.setClassName(getClass().getName());
623        event.setPackageName(mContext.getPackageName());
624
625        LayoutParams params = getWindow().getAttributes();
626        boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
627            (params.height == LayoutParams.FILL_PARENT);
628        event.setFullScreen(isFullScreen);
629
630        return false;
631    }
632
633    /**
634     * @see Activity#onCreatePanelView(int)
635     */
636    public View onCreatePanelView(int featureId) {
637        return null;
638    }
639
640    /**
641     * @see Activity#onCreatePanelMenu(int, Menu)
642     */
643    public boolean onCreatePanelMenu(int featureId, Menu menu) {
644        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
645            return onCreateOptionsMenu(menu);
646        }
647
648        return false;
649    }
650
651    /**
652     * @see Activity#onPreparePanel(int, View, Menu)
653     */
654    public boolean onPreparePanel(int featureId, View view, Menu menu) {
655        if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
656            boolean goforit = onPrepareOptionsMenu(menu);
657            return goforit && menu.hasVisibleItems();
658        }
659        return true;
660    }
661
662    /**
663     * @see Activity#onMenuOpened(int, Menu)
664     */
665    public boolean onMenuOpened(int featureId, Menu menu) {
666        return true;
667    }
668
669    /**
670     * @see Activity#onMenuItemSelected(int, MenuItem)
671     */
672    public boolean onMenuItemSelected(int featureId, MenuItem item) {
673        return false;
674    }
675
676    /**
677     * @see Activity#onPanelClosed(int, Menu)
678     */
679    public void onPanelClosed(int featureId, Menu menu) {
680    }
681
682    /**
683     * It is usually safe to proxy this call to the owner activity's
684     * {@link Activity#onCreateOptionsMenu(Menu)} if the client desires the same
685     * menu for this Dialog.
686     *
687     * @see Activity#onCreateOptionsMenu(Menu)
688     * @see #getOwnerActivity()
689     */
690    public boolean onCreateOptionsMenu(Menu menu) {
691        return true;
692    }
693
694    /**
695     * It is usually safe to proxy this call to the owner activity's
696     * {@link Activity#onPrepareOptionsMenu(Menu)} if the client desires the
697     * same menu for this Dialog.
698     *
699     * @see Activity#onPrepareOptionsMenu(Menu)
700     * @see #getOwnerActivity()
701     */
702    public boolean onPrepareOptionsMenu(Menu menu) {
703        return true;
704    }
705
706    /**
707     * @see Activity#onOptionsItemSelected(MenuItem)
708     */
709    public boolean onOptionsItemSelected(MenuItem item) {
710        return false;
711    }
712
713    /**
714     * @see Activity#onOptionsMenuClosed(Menu)
715     */
716    public void onOptionsMenuClosed(Menu menu) {
717    }
718
719    /**
720     * @see Activity#openOptionsMenu()
721     */
722    public void openOptionsMenu() {
723        mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
724    }
725
726    /**
727     * @see Activity#closeOptionsMenu()
728     */
729    public void closeOptionsMenu() {
730        mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
731    }
732
733    /**
734     * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo)
735     */
736    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
737    }
738
739    /**
740     * @see Activity#registerForContextMenu(View)
741     */
742    public void registerForContextMenu(View view) {
743        view.setOnCreateContextMenuListener(this);
744    }
745
746    /**
747     * @see Activity#unregisterForContextMenu(View)
748     */
749    public void unregisterForContextMenu(View view) {
750        view.setOnCreateContextMenuListener(null);
751    }
752
753    /**
754     * @see Activity#openContextMenu(View)
755     */
756    public void openContextMenu(View view) {
757        view.showContextMenu();
758    }
759
760    /**
761     * @see Activity#onContextItemSelected(MenuItem)
762     */
763    public boolean onContextItemSelected(MenuItem item) {
764        return false;
765    }
766
767    /**
768     * @see Activity#onContextMenuClosed(Menu)
769     */
770    public void onContextMenuClosed(Menu menu) {
771    }
772
773    /**
774     * This hook is called when the user signals the desire to start a search.
775     */
776    public boolean onSearchRequested() {
777        // not during dialogs, no.
778        return false;
779    }
780
781
782    /**
783     * Request that key events come to this dialog. Use this if your
784     * dialog has no views with focus, but the dialog still wants
785     * a chance to process key events.
786     *
787     * @param get true if the dialog should receive key events, false otherwise
788     * @see android.view.Window#takeKeyEvents
789     */
790    public void takeKeyEvents(boolean get) {
791        mWindow.takeKeyEvents(get);
792    }
793
794    /**
795     * Enable extended window features.  This is a convenience for calling
796     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
797     *
798     * @param featureId The desired feature as defined in
799     *                  {@link android.view.Window}.
800     * @return Returns true if the requested feature is supported and now
801     *         enabled.
802     *
803     * @see android.view.Window#requestFeature
804     */
805    public final boolean requestWindowFeature(int featureId) {
806        return getWindow().requestFeature(featureId);
807    }
808
809    /**
810     * Convenience for calling
811     * {@link android.view.Window#setFeatureDrawableResource}.
812     */
813    public final void setFeatureDrawableResource(int featureId, int resId) {
814        getWindow().setFeatureDrawableResource(featureId, resId);
815    }
816
817    /**
818     * Convenience for calling
819     * {@link android.view.Window#setFeatureDrawableUri}.
820     */
821    public final void setFeatureDrawableUri(int featureId, Uri uri) {
822        getWindow().setFeatureDrawableUri(featureId, uri);
823    }
824
825    /**
826     * Convenience for calling
827     * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
828     */
829    public final void setFeatureDrawable(int featureId, Drawable drawable) {
830        getWindow().setFeatureDrawable(featureId, drawable);
831    }
832
833    /**
834     * Convenience for calling
835     * {@link android.view.Window#setFeatureDrawableAlpha}.
836     */
837    public final void setFeatureDrawableAlpha(int featureId, int alpha) {
838        getWindow().setFeatureDrawableAlpha(featureId, alpha);
839    }
840
841    public LayoutInflater getLayoutInflater() {
842        return getWindow().getLayoutInflater();
843    }
844
845    /**
846     * Sets whether this dialog is cancelable with the
847     * {@link KeyEvent#KEYCODE_BACK BACK} key.
848     */
849    public void setCancelable(boolean flag) {
850        mCancelable = flag;
851    }
852
853    /**
854     * Sets whether this dialog is canceled when touched outside the window's
855     * bounds. If setting to true, the dialog is set to be cancelable if not
856     * already set.
857     *
858     * @param cancel Whether the dialog should be canceled when touched outside
859     *            the window.
860     */
861    public void setCanceledOnTouchOutside(boolean cancel) {
862        if (cancel && !mCancelable) {
863            mCancelable = true;
864        }
865
866        mCanceledOnTouchOutside = cancel;
867    }
868
869    /**
870     * Cancel the dialog.  This is essentially the same as calling {@link #dismiss()}, but it will
871     * also call your {@link DialogInterface.OnCancelListener} (if registered).
872     */
873    public void cancel() {
874        if (mCancelMessage != null) {
875
876            // Obtain a new message so this dialog can be re-used
877            Message.obtain(mCancelMessage).sendToTarget();
878        }
879        dismiss();
880    }
881
882    /**
883     * Set a listener to be invoked when the dialog is canceled.
884     * <p>
885     * This will only be invoked when the dialog is canceled, if the creator
886     * needs to know when it is dismissed in general, use
887     * {@link #setOnDismissListener}.
888     *
889     * @param listener The {@link DialogInterface.OnCancelListener} to use.
890     */
891    public void setOnCancelListener(final OnCancelListener listener) {
892        if (listener != null) {
893            mCancelMessage = mDismissCancelHandler.obtainMessage(CANCEL, listener);
894        } else {
895            mCancelMessage = null;
896        }
897    }
898
899    /**
900     * Set a message to be sent when the dialog is canceled.
901     * @param msg The msg to send when the dialog is canceled.
902     * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener)
903     */
904    public void setCancelMessage(final Message msg) {
905        mCancelMessage = msg;
906    }
907
908    /**
909     * Set a listener to be invoked when the dialog is dismissed.
910     * @param listener The {@link DialogInterface.OnDismissListener} to use.
911     */
912    public void setOnDismissListener(final OnDismissListener listener) {
913        if (listener != null) {
914            mDismissMessage = mDismissCancelHandler.obtainMessage(DISMISS, listener);
915        } else {
916            mDismissMessage = null;
917        }
918    }
919
920    /**
921     * Set a message to be sent when the dialog is dismissed.
922     * @param msg The msg to send when the dialog is dismissed.
923     */
924    public void setDismissMessage(final Message msg) {
925        mDismissMessage = msg;
926    }
927
928    /**
929     * By default, this will use the owner Activity's suggested stream type.
930     *
931     * @see Activity#setVolumeControlStream(int)
932     * @see #setOwnerActivity(Activity)
933     */
934    public final void setVolumeControlStream(int streamType) {
935        getWindow().setVolumeControlStream(streamType);
936    }
937
938    /**
939     * @see Activity#getVolumeControlStream()
940     */
941    public final int getVolumeControlStream() {
942        return getWindow().getVolumeControlStream();
943    }
944
945    /**
946     * Sets the callback that will be called if a key is dispatched to the dialog.
947     */
948    public void setOnKeyListener(final OnKeyListener onKeyListener) {
949        mOnKeyListener = onKeyListener;
950    }
951
952    private static final int DISMISS = 0x43;
953    private static final int CANCEL = 0x44;
954
955    private Handler mDismissCancelHandler;
956
957    private static final class DismissCancelHandler extends Handler {
958        private WeakReference<DialogInterface> mDialog;
959
960        public DismissCancelHandler(Dialog dialog) {
961            mDialog = new WeakReference<DialogInterface>(dialog);
962        }
963
964        @Override
965        public void handleMessage(Message msg) {
966            switch (msg.what) {
967                case DISMISS:
968                    ((OnDismissListener) msg.obj).onDismiss(mDialog.get());
969                    break;
970                case CANCEL:
971                    ((OnCancelListener) msg.obj).onCancel(mDialog.get());
972                    break;
973            }
974        }
975    }
976}
977