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