Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2010 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 android.content.Context;
     20 import android.content.DialogInterface;
     21 import android.os.Bundle;
     22 import android.view.LayoutInflater;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 import android.view.Window;
     26 import android.view.WindowManager;
     27 
     28 import java.io.FileDescriptor;
     29 import java.io.PrintWriter;
     30 
     31 /**
     32  * A fragment that displays a dialog window, floating on top of its
     33  * activity's window.  This fragment contains a Dialog object, which it
     34  * displays as appropriate based on the fragment's state.  Control of
     35  * the dialog (deciding when to show, hide, dismiss it) should be done through
     36  * the API here, not with direct calls on the dialog.
     37  *
     38  * <p>Implementations should override this class and implement
     39  * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} to supply the
     40  * content of the dialog.  Alternatively, they can override
     41  * {@link #onCreateDialog(Bundle)} to create an entirely custom dialog, such
     42  * as an AlertDialog, with its own content.
     43  *
     44  * <p>Topics covered here:
     45  * <ol>
     46  * <li><a href="#Lifecycle">Lifecycle</a>
     47  * <li><a href="#BasicDialog">Basic Dialog</a>
     48  * <li><a href="#AlertDialog">Alert Dialog</a>
     49  * <li><a href="#DialogOrEmbed">Selecting Between Dialog or Embedding</a>
     50  * </ol>
     51  *
     52  * <a name="Lifecycle"></a>
     53  * <h3>Lifecycle</h3>
     54  *
     55  * <p>DialogFragment does various things to keep the fragment's lifecycle
     56  * driving it, instead of the Dialog.  Note that dialogs are generally
     57  * autonomous entities -- they are their own window, receiving their own
     58  * input events, and often deciding on their own when to disappear (by
     59  * receiving a back key event or the user clicking on a button).
     60  *
     61  * <p>DialogFragment needs to ensure that what is happening with the Fragment
     62  * and Dialog states remains consistent.  To do this, it watches for dismiss
     63  * events from the dialog and takes care of removing its own state when they
     64  * happen.  This means you should use {@link #show(FragmentManager, String)}
     65  * or {@link #show(FragmentTransaction, String)} to add an instance of
     66  * DialogFragment to your UI, as these keep track of how DialogFragment should
     67  * remove itself when the dialog is dismissed.
     68  *
     69  * <a name="BasicDialog"></a>
     70  * <h3>Basic Dialog</h3>
     71  *
     72  * <p>The simplest use of DialogFragment is as a floating container for the
     73  * fragment's view hierarchy.  A simple implementation may look like this:
     74  *
     75  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java
     76  *      dialog}
     77  *
     78  * <p>An example showDialog() method on the Activity could be:
     79  *
     80  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java
     81  *      add_dialog}
     82  *
     83  * <p>This removes any currently shown dialog, creates a new DialogFragment
     84  * with an argument, and shows it as a new state on the back stack.  When the
     85  * transaction is popped, the current DialogFragment and its Dialog will be
     86  * destroyed, and the previous one (if any) re-shown.  Note that in this case
     87  * DialogFragment will take care of popping the transaction of the Dialog
     88  * is dismissed separately from it.
     89  *
     90  * <a name="AlertDialog"></a>
     91  * <h3>Alert Dialog</h3>
     92  *
     93  * <p>Instead of (or in addition to) implementing {@link #onCreateView} to
     94  * generate the view hierarchy inside of a dialog, you may implement
     95  * {@link #onCreateDialog(Bundle)} to create your own custom Dialog object.
     96  *
     97  * <p>This is most useful for creating an {@link AlertDialog}, allowing you
     98  * to display standard alerts to the user that are managed by a fragment.
     99  * A simple example implementation of this is:
    100  *
    101  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java
    102  *      dialog}
    103  *
    104  * <p>The activity creating this fragment may have the following methods to
    105  * show the dialog and receive results from it:
    106  *
    107  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java
    108  *      activity}
    109  *
    110  * <p>Note that in this case the fragment is not placed on the back stack, it
    111  * is just added as an indefinitely running fragment.  Because dialogs normally
    112  * are modal, this will still operate as a back stack, since the dialog will
    113  * capture user input until it is dismissed.  When it is dismissed, DialogFragment
    114  * will take care of removing itself from its fragment manager.
    115  *
    116  * <a name="DialogOrEmbed"></a>
    117  * <h3>Selecting Between Dialog or Embedding</h3>
    118  *
    119  * <p>A DialogFragment can still optionally be used as a normal fragment, if
    120  * desired.  This is useful if you have a fragment that in some cases should
    121  * be shown as a dialog and others embedded in a larger UI.  This behavior
    122  * will normally be automatically selected for you based on how you are using
    123  * the fragment, but can be customized with {@link #setShowsDialog(boolean)}.
    124  *
    125  * <p>For example, here is a simple dialog fragment:
    126  *
    127  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
    128  *      dialog}
    129  *
    130  * <p>An instance of this fragment can be created and shown as a dialog:
    131  *
    132  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
    133  *      show_dialog}
    134  *
    135  * <p>It can also be added as content in a view hierarchy:
    136  *
    137  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
    138  *      embed}
    139  */
    140 public class DialogFragment extends Fragment
    141         implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
    142 
    143     /**
    144      * Style for {@link #setStyle(int, int)}: a basic,
    145      * normal dialog.
    146      */
    147     public static final int STYLE_NORMAL = 0;
    148 
    149     /**
    150      * Style for {@link #setStyle(int, int)}: don't include
    151      * a title area.
    152      */
    153     public static final int STYLE_NO_TITLE = 1;
    154 
    155     /**
    156      * Style for {@link #setStyle(int, int)}: don't draw
    157      * any frame at all; the view hierarchy returned by {@link #onCreateView}
    158      * is entirely responsible for drawing the dialog.
    159      */
    160     public static final int STYLE_NO_FRAME = 2;
    161 
    162     /**
    163      * Style for {@link #setStyle(int, int)}: like
    164      * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
    165      * The user can not touch it, and its window will not receive input focus.
    166      */
    167     public static final int STYLE_NO_INPUT = 3;
    168 
    169     private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
    170     private static final String SAVED_STYLE = "android:style";
    171     private static final String SAVED_THEME = "android:theme";
    172     private static final String SAVED_CANCELABLE = "android:cancelable";
    173     private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
    174     private static final String SAVED_BACK_STACK_ID = "android:backStackId";
    175 
    176     int mStyle = STYLE_NORMAL;
    177     int mTheme = 0;
    178     boolean mCancelable = true;
    179     boolean mShowsDialog = true;
    180     int mBackStackId = -1;
    181 
    182     Dialog mDialog;
    183     boolean mViewDestroyed;
    184     boolean mDismissed;
    185     boolean mShownByMe;
    186 
    187     public DialogFragment() {
    188     }
    189 
    190     /**
    191      * Call to customize the basic appearance and behavior of the
    192      * fragment's dialog.  This can be used for some common dialog behaviors,
    193      * taking care of selecting flags, theme, and other options for you.  The
    194      * same effect can be achieve by manually setting Dialog and Window
    195      * attributes yourself.  Calling this after the fragment's Dialog is
    196      * created will have no effect.
    197      *
    198      * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
    199      * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
    200      * {@link #STYLE_NO_INPUT}.
    201      * @param theme Optional custom theme.  If 0, an appropriate theme (based
    202      * on the style) will be selected for you.
    203      */
    204     public void setStyle(int style, int theme) {
    205         mStyle = style;
    206         if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
    207             mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame;
    208         }
    209         if (theme != 0) {
    210             mTheme = theme;
    211         }
    212     }
    213 
    214     /**
    215      * Display the dialog, adding the fragment to the given FragmentManager.  This
    216      * is a convenience for explicitly creating a transaction, adding the
    217      * fragment to it with the given tag, and committing it.  This does
    218      * <em>not</em> add the transaction to the back stack.  When the fragment
    219      * is dismissed, a new transaction will be executed to remove it from
    220      * the activity.
    221      * @param manager The FragmentManager this fragment will be added to.
    222      * @param tag The tag for this fragment, as per
    223      * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
    224      */
    225     public void show(FragmentManager manager, String tag) {
    226         mDismissed = false;
    227         mShownByMe = true;
    228         FragmentTransaction ft = manager.beginTransaction();
    229         ft.add(this, tag);
    230         ft.commit();
    231     }
    232 
    233     /** {@hide} */
    234     public void showAllowingStateLoss(FragmentManager manager, String tag) {
    235         mDismissed = false;
    236         mShownByMe = true;
    237         FragmentTransaction ft = manager.beginTransaction();
    238         ft.add(this, tag);
    239         ft.commitAllowingStateLoss();
    240     }
    241 
    242     /**
    243      * Display the dialog, adding the fragment using an existing transaction
    244      * and then committing the transaction.
    245      * @param transaction An existing transaction in which to add the fragment.
    246      * @param tag The tag for this fragment, as per
    247      * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
    248      * @return Returns the identifier of the committed transaction, as per
    249      * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
    250      */
    251     public int show(FragmentTransaction transaction, String tag) {
    252         mDismissed = false;
    253         mShownByMe = true;
    254         transaction.add(this, tag);
    255         mViewDestroyed = false;
    256         mBackStackId = transaction.commit();
    257         return mBackStackId;
    258     }
    259 
    260     /**
    261      * Dismiss the fragment and its dialog.  If the fragment was added to the
    262      * back stack, all back stack state up to and including this entry will
    263      * be popped.  Otherwise, a new transaction will be committed to remove
    264      * the fragment.
    265      */
    266     public void dismiss() {
    267         dismissInternal(false);
    268     }
    269 
    270     /**
    271      * Version of {@link #dismiss()} that uses
    272      * {@link FragmentTransaction#commitAllowingStateLoss()
    273      * FragmentTransaction.commitAllowingStateLoss()}.  See linked
    274      * documentation for further details.
    275      */
    276     public void dismissAllowingStateLoss() {
    277         dismissInternal(true);
    278     }
    279 
    280     void dismissInternal(boolean allowStateLoss) {
    281         if (mDismissed) {
    282             return;
    283         }
    284         mDismissed = true;
    285         mShownByMe = false;
    286         if (mDialog != null) {
    287             mDialog.dismiss();
    288             mDialog = null;
    289         }
    290         mViewDestroyed = true;
    291         if (mBackStackId >= 0) {
    292             getFragmentManager().popBackStack(mBackStackId,
    293                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
    294             mBackStackId = -1;
    295         } else {
    296             FragmentTransaction ft = getFragmentManager().beginTransaction();
    297             ft.remove(this);
    298             if (allowStateLoss) {
    299                 ft.commitAllowingStateLoss();
    300             } else {
    301                 ft.commit();
    302             }
    303         }
    304     }
    305 
    306     public Dialog getDialog() {
    307         return mDialog;
    308     }
    309 
    310     public int getTheme() {
    311         return mTheme;
    312     }
    313 
    314     /**
    315      * Control whether the shown Dialog is cancelable.  Use this instead of
    316      * directly calling {@link Dialog#setCancelable(boolean)
    317      * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
    318      * its behavior based on this.
    319      *
    320      * @param cancelable If true, the dialog is cancelable.  The default
    321      * is true.
    322      */
    323     public void setCancelable(boolean cancelable) {
    324         mCancelable = cancelable;
    325         if (mDialog != null) mDialog.setCancelable(cancelable);
    326     }
    327 
    328     /**
    329      * Return the current value of {@link #setCancelable(boolean)}.
    330      */
    331     public boolean isCancelable() {
    332         return mCancelable;
    333     }
    334 
    335     /**
    336      * Controls whether this fragment should be shown in a dialog.  If not
    337      * set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
    338      * and the fragment's view hierarchy will thus not be added to it.  This
    339      * allows you to instead use it as a normal fragment (embedded inside of
    340      * its activity).
    341      *
    342      * <p>This is normally set for you based on whether the fragment is
    343      * associated with a container view ID passed to
    344      * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
    345      * If the fragment was added with a container, setShowsDialog will be
    346      * initialized to false; otherwise, it will be true.
    347      *
    348      * @param showsDialog If true, the fragment will be displayed in a Dialog.
    349      * If false, no Dialog will be created and the fragment's view hierarchly
    350      * left undisturbed.
    351      */
    352     public void setShowsDialog(boolean showsDialog) {
    353         mShowsDialog = showsDialog;
    354     }
    355 
    356     /**
    357      * Return the current value of {@link #setShowsDialog(boolean)}.
    358      */
    359     public boolean getShowsDialog() {
    360         return mShowsDialog;
    361     }
    362 
    363     @Override
    364     public void onAttach(Context context) {
    365         super.onAttach(context);
    366         if (!mShownByMe) {
    367             // If not explicitly shown through our API, take this as an
    368             // indication that the dialog is no longer dismissed.
    369             mDismissed = false;
    370         }
    371     }
    372 
    373     @Override
    374     public void onDetach() {
    375         super.onDetach();
    376         if (!mShownByMe && !mDismissed) {
    377             // The fragment was not shown by a direct call here, it is not
    378             // dismissed, and now it is being detached...  well, okay, thou
    379             // art now dismissed.  Have fun.
    380             mDismissed = true;
    381         }
    382     }
    383 
    384     @Override
    385     public void onCreate(Bundle savedInstanceState) {
    386         super.onCreate(savedInstanceState);
    387 
    388         mShowsDialog = mContainerId == 0;
    389 
    390         if (savedInstanceState != null) {
    391             mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
    392             mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
    393             mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
    394             mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
    395             mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
    396         }
    397     }
    398 
    399     /** @hide */
    400     @Override
    401     public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
    402         if (!mShowsDialog) {
    403             return super.onGetLayoutInflater(savedInstanceState);
    404         }
    405 
    406         mDialog = onCreateDialog(savedInstanceState);
    407         switch (mStyle) {
    408             case STYLE_NO_INPUT:
    409                 mDialog.getWindow().addFlags(
    410                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
    411                         WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
    412                 // fall through...
    413             case STYLE_NO_FRAME:
    414             case STYLE_NO_TITLE:
    415                 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    416         }
    417         if (mDialog != null) {
    418             return (LayoutInflater)mDialog.getContext().getSystemService(
    419                     Context.LAYOUT_INFLATER_SERVICE);
    420         }
    421         return (LayoutInflater) mHost.getContext().getSystemService(
    422                 Context.LAYOUT_INFLATER_SERVICE);
    423     }
    424 
    425     /**
    426      * Override to build your own custom Dialog container.  This is typically
    427      * used to show an AlertDialog instead of a generic Dialog; when doing so,
    428      * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need
    429      * to be implemented since the AlertDialog takes care of its own content.
    430      *
    431      * <p>This method will be called after {@link #onCreate(Bundle)} and
    432      * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.  The
    433      * default implementation simply instantiates and returns a {@link Dialog}
    434      * class.
    435      *
    436      * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
    437      * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
    438      * Dialog.setOnDismissListener} callbacks.  You must not set them yourself.</em>
    439      * To find out about these events, override {@link #onCancel(DialogInterface)}
    440      * and {@link #onDismiss(DialogInterface)}.</p>
    441      *
    442      * @param savedInstanceState The last saved instance state of the Fragment,
    443      * or null if this is a freshly created Fragment.
    444      *
    445      * @return Return a new Dialog instance to be displayed by the Fragment.
    446      */
    447     public Dialog onCreateDialog(Bundle savedInstanceState) {
    448         return new Dialog(getActivity(), getTheme());
    449     }
    450 
    451     public void onCancel(DialogInterface dialog) {
    452     }
    453 
    454     public void onDismiss(DialogInterface dialog) {
    455         if (!mViewDestroyed) {
    456             // Note: we need to use allowStateLoss, because the dialog
    457             // dispatches this asynchronously so we can receive the call
    458             // after the activity is paused.  Worst case, when the user comes
    459             // back to the activity they see the dialog again.
    460             dismissInternal(true);
    461         }
    462     }
    463 
    464     @Override
    465     public void onActivityCreated(Bundle savedInstanceState) {
    466         super.onActivityCreated(savedInstanceState);
    467 
    468         if (!mShowsDialog) {
    469             return;
    470         }
    471 
    472         View view = getView();
    473         if (view != null) {
    474             if (view.getParent() != null) {
    475                 throw new IllegalStateException(
    476                         "DialogFragment can not be attached to a container view");
    477             }
    478             mDialog.setContentView(view);
    479         }
    480         final Activity activity = getActivity();
    481         if (activity != null) {
    482             mDialog.setOwnerActivity(activity);
    483         }
    484         mDialog.setCancelable(mCancelable);
    485         if (!mDialog.takeCancelAndDismissListeners("DialogFragment", this, this)) {
    486             throw new IllegalStateException(
    487                     "You can not set Dialog's OnCancelListener or OnDismissListener");
    488         }
    489         if (savedInstanceState != null) {
    490             Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
    491             if (dialogState != null) {
    492                 mDialog.onRestoreInstanceState(dialogState);
    493             }
    494         }
    495     }
    496 
    497     @Override
    498     public void onStart() {
    499         super.onStart();
    500         if (mDialog != null) {
    501             mViewDestroyed = false;
    502             mDialog.show();
    503         }
    504     }
    505 
    506     @Override
    507     public void onSaveInstanceState(Bundle outState) {
    508         super.onSaveInstanceState(outState);
    509         if (mDialog != null) {
    510             Bundle dialogState = mDialog.onSaveInstanceState();
    511             if (dialogState != null) {
    512                 outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
    513             }
    514         }
    515         if (mStyle != STYLE_NORMAL) {
    516             outState.putInt(SAVED_STYLE, mStyle);
    517         }
    518         if (mTheme != 0) {
    519             outState.putInt(SAVED_THEME, mTheme);
    520         }
    521         if (!mCancelable) {
    522             outState.putBoolean(SAVED_CANCELABLE, mCancelable);
    523         }
    524         if (!mShowsDialog) {
    525             outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
    526         }
    527         if (mBackStackId != -1) {
    528             outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
    529         }
    530     }
    531 
    532     @Override
    533     public void onStop() {
    534         super.onStop();
    535         if (mDialog != null) {
    536             mDialog.hide();
    537         }
    538     }
    539 
    540     /**
    541      * Remove dialog.
    542      */
    543     @Override
    544     public void onDestroyView() {
    545         super.onDestroyView();
    546         if (mDialog != null) {
    547             // Set removed here because this dismissal is just to hide
    548             // the dialog -- we don't want this to cause the fragment to
    549             // actually be removed.
    550             mViewDestroyed = true;
    551             mDialog.dismiss();
    552             mDialog = null;
    553         }
    554     }
    555 
    556     @Override
    557     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
    558         super.dump(prefix, fd, writer, args);
    559         writer.print(prefix); writer.println("DialogFragment:");
    560         writer.print(prefix); writer.print("  mStyle="); writer.print(mStyle);
    561                 writer.print(" mTheme=0x"); writer.println(Integer.toHexString(mTheme));
    562         writer.print(prefix); writer.print("  mCancelable="); writer.print(mCancelable);
    563                 writer.print(" mShowsDialog="); writer.print(mShowsDialog);
    564                 writer.print(" mBackStackId="); writer.println(mBackStackId);
    565         writer.print(prefix); writer.print("  mDialog="); writer.println(mDialog);
    566         writer.print(prefix); writer.print("  mViewDestroyed="); writer.print(mViewDestroyed);
    567                 writer.print(" mDismissed="); writer.print(mDismissed);
    568                 writer.print(" mShownByMe="); writer.println(mShownByMe);
    569     }
    570 }
    571