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