• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
141  *      {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
142  *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
143  */
144 @Deprecated
145 public class DialogFragment extends Fragment
146         implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
147 
148     /**
149      * Style for {@link #setStyle(int, int)}: a basic,
150      * normal dialog.
151      */
152     public static final int STYLE_NORMAL = 0;
153 
154     /**
155      * Style for {@link #setStyle(int, int)}: don't include
156      * a title area.
157      */
158     public static final int STYLE_NO_TITLE = 1;
159 
160     /**
161      * Style for {@link #setStyle(int, int)}: don't draw
162      * any frame at all; the view hierarchy returned by {@link #onCreateView}
163      * is entirely responsible for drawing the dialog.
164      */
165     public static final int STYLE_NO_FRAME = 2;
166 
167     /**
168      * Style for {@link #setStyle(int, int)}: like
169      * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
170      * The user can not touch it, and its window will not receive input focus.
171      */
172     public static final int STYLE_NO_INPUT = 3;
173 
174     private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
175     private static final String SAVED_STYLE = "android:style";
176     private static final String SAVED_THEME = "android:theme";
177     private static final String SAVED_CANCELABLE = "android:cancelable";
178     private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
179     private static final String SAVED_BACK_STACK_ID = "android:backStackId";
180 
181     int mStyle = STYLE_NORMAL;
182     int mTheme = 0;
183     boolean mCancelable = true;
184     boolean mShowsDialog = true;
185     int mBackStackId = -1;
186 
187     Dialog mDialog;
188     boolean mViewDestroyed;
189     boolean mDismissed;
190     boolean mShownByMe;
191 
DialogFragment()192     public DialogFragment() {
193     }
194 
195     /**
196      * Call to customize the basic appearance and behavior of the
197      * fragment's dialog.  This can be used for some common dialog behaviors,
198      * taking care of selecting flags, theme, and other options for you.  The
199      * same effect can be achieve by manually setting Dialog and Window
200      * attributes yourself.  Calling this after the fragment's Dialog is
201      * created will have no effect.
202      *
203      * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
204      * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
205      * {@link #STYLE_NO_INPUT}.
206      * @param theme Optional custom theme.  If 0, an appropriate theme (based
207      * on the style) will be selected for you.
208      */
setStyle(int style, int theme)209     public void setStyle(int style, int theme) {
210         mStyle = style;
211         if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
212             mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame;
213         }
214         if (theme != 0) {
215             mTheme = theme;
216         }
217     }
218 
219     /**
220      * Display the dialog, adding the fragment to the given FragmentManager.  This
221      * is a convenience for explicitly creating a transaction, adding the
222      * fragment to it with the given tag, and committing it.  This does
223      * <em>not</em> add the transaction to the back stack.  When the fragment
224      * is dismissed, a new transaction will be executed to remove it from
225      * the activity.
226      * @param manager The FragmentManager this fragment will be added to.
227      * @param tag The tag for this fragment, as per
228      * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
229      */
show(FragmentManager manager, String tag)230     public void show(FragmentManager manager, String tag) {
231         mDismissed = false;
232         mShownByMe = true;
233         FragmentTransaction ft = manager.beginTransaction();
234         ft.add(this, tag);
235         ft.commit();
236     }
237 
238     /** {@hide} */
showAllowingStateLoss(FragmentManager manager, String tag)239     public void showAllowingStateLoss(FragmentManager manager, String tag) {
240         mDismissed = false;
241         mShownByMe = true;
242         FragmentTransaction ft = manager.beginTransaction();
243         ft.add(this, tag);
244         ft.commitAllowingStateLoss();
245     }
246 
247     /**
248      * Display the dialog, adding the fragment using an existing transaction
249      * and then committing the transaction.
250      * @param transaction An existing transaction in which to add the fragment.
251      * @param tag The tag for this fragment, as per
252      * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
253      * @return Returns the identifier of the committed transaction, as per
254      * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
255      */
show(FragmentTransaction transaction, String tag)256     public int show(FragmentTransaction transaction, String tag) {
257         mDismissed = false;
258         mShownByMe = true;
259         transaction.add(this, tag);
260         mViewDestroyed = false;
261         mBackStackId = transaction.commit();
262         return mBackStackId;
263     }
264 
265     /**
266      * Dismiss the fragment and its dialog.  If the fragment was added to the
267      * back stack, all back stack state up to and including this entry will
268      * be popped.  Otherwise, a new transaction will be committed to remove
269      * the fragment.
270      */
dismiss()271     public void dismiss() {
272         dismissInternal(false);
273     }
274 
275     /**
276      * Version of {@link #dismiss()} that uses
277      * {@link FragmentTransaction#commitAllowingStateLoss()
278      * FragmentTransaction.commitAllowingStateLoss()}.  See linked
279      * documentation for further details.
280      */
dismissAllowingStateLoss()281     public void dismissAllowingStateLoss() {
282         dismissInternal(true);
283     }
284 
dismissInternal(boolean allowStateLoss)285     void dismissInternal(boolean allowStateLoss) {
286         if (mDismissed) {
287             return;
288         }
289         mDismissed = true;
290         mShownByMe = false;
291         if (mDialog != null) {
292             mDialog.dismiss();
293             mDialog = null;
294         }
295         mViewDestroyed = true;
296         if (mBackStackId >= 0) {
297             getFragmentManager().popBackStack(mBackStackId,
298                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
299             mBackStackId = -1;
300         } else {
301             FragmentTransaction ft = getFragmentManager().beginTransaction();
302             ft.remove(this);
303             if (allowStateLoss) {
304                 ft.commitAllowingStateLoss();
305             } else {
306                 ft.commit();
307             }
308         }
309     }
310 
getDialog()311     public Dialog getDialog() {
312         return mDialog;
313     }
314 
getTheme()315     public int getTheme() {
316         return mTheme;
317     }
318 
319     /**
320      * Control whether the shown Dialog is cancelable.  Use this instead of
321      * directly calling {@link Dialog#setCancelable(boolean)
322      * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
323      * its behavior based on this.
324      *
325      * @param cancelable If true, the dialog is cancelable.  The default
326      * is true.
327      */
setCancelable(boolean cancelable)328     public void setCancelable(boolean cancelable) {
329         mCancelable = cancelable;
330         if (mDialog != null) mDialog.setCancelable(cancelable);
331     }
332 
333     /**
334      * Return the current value of {@link #setCancelable(boolean)}.
335      */
isCancelable()336     public boolean isCancelable() {
337         return mCancelable;
338     }
339 
340     /**
341      * Controls whether this fragment should be shown in a dialog.  If not
342      * set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
343      * and the fragment's view hierarchy will thus not be added to it.  This
344      * allows you to instead use it as a normal fragment (embedded inside of
345      * its activity).
346      *
347      * <p>This is normally set for you based on whether the fragment is
348      * associated with a container view ID passed to
349      * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
350      * If the fragment was added with a container, setShowsDialog will be
351      * initialized to false; otherwise, it will be true.
352      *
353      * @param showsDialog If true, the fragment will be displayed in a Dialog.
354      * If false, no Dialog will be created and the fragment's view hierarchly
355      * left undisturbed.
356      */
setShowsDialog(boolean showsDialog)357     public void setShowsDialog(boolean showsDialog) {
358         mShowsDialog = showsDialog;
359     }
360 
361     /**
362      * Return the current value of {@link #setShowsDialog(boolean)}.
363      */
getShowsDialog()364     public boolean getShowsDialog() {
365         return mShowsDialog;
366     }
367 
368     @Override
onAttach(Context context)369     public void onAttach(Context context) {
370         super.onAttach(context);
371         if (!mShownByMe) {
372             // If not explicitly shown through our API, take this as an
373             // indication that the dialog is no longer dismissed.
374             mDismissed = false;
375         }
376     }
377 
378     @Override
onDetach()379     public void onDetach() {
380         super.onDetach();
381         if (!mShownByMe && !mDismissed) {
382             // The fragment was not shown by a direct call here, it is not
383             // dismissed, and now it is being detached...  well, okay, thou
384             // art now dismissed.  Have fun.
385             mDismissed = true;
386         }
387     }
388 
389     @Override
onCreate(Bundle savedInstanceState)390     public void onCreate(Bundle savedInstanceState) {
391         super.onCreate(savedInstanceState);
392 
393         mShowsDialog = mContainerId == 0;
394 
395         if (savedInstanceState != null) {
396             mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
397             mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
398             mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
399             mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
400             mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
401         }
402     }
403 
404     /** @hide */
405     @Override
onGetLayoutInflater(Bundle savedInstanceState)406     public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
407         if (!mShowsDialog) {
408             return super.onGetLayoutInflater(savedInstanceState);
409         }
410 
411         mDialog = onCreateDialog(savedInstanceState);
412         switch (mStyle) {
413             case STYLE_NO_INPUT:
414                 mDialog.getWindow().addFlags(
415                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
416                         WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
417                 // fall through...
418             case STYLE_NO_FRAME:
419             case STYLE_NO_TITLE:
420                 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
421         }
422         if (mDialog != null) {
423             return (LayoutInflater)mDialog.getContext().getSystemService(
424                     Context.LAYOUT_INFLATER_SERVICE);
425         }
426         return (LayoutInflater) mHost.getContext().getSystemService(
427                 Context.LAYOUT_INFLATER_SERVICE);
428     }
429 
430     /**
431      * Override to build your own custom Dialog container.  This is typically
432      * used to show an AlertDialog instead of a generic Dialog; when doing so,
433      * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need
434      * to be implemented since the AlertDialog takes care of its own content.
435      *
436      * <p>This method will be called after {@link #onCreate(Bundle)} and
437      * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.  The
438      * default implementation simply instantiates and returns a {@link Dialog}
439      * class.
440      *
441      * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
442      * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
443      * Dialog.setOnDismissListener} callbacks.  You must not set them yourself.</em>
444      * To find out about these events, override {@link #onCancel(DialogInterface)}
445      * and {@link #onDismiss(DialogInterface)}.</p>
446      *
447      * @param savedInstanceState The last saved instance state of the Fragment,
448      * or null if this is a freshly created Fragment.
449      *
450      * @return Return a new Dialog instance to be displayed by the Fragment.
451      */
onCreateDialog(Bundle savedInstanceState)452     public Dialog onCreateDialog(Bundle savedInstanceState) {
453         return new Dialog(getActivity(), getTheme());
454     }
455 
onCancel(DialogInterface dialog)456     public void onCancel(DialogInterface dialog) {
457     }
458 
onDismiss(DialogInterface dialog)459     public void onDismiss(DialogInterface dialog) {
460         if (!mViewDestroyed) {
461             // Note: we need to use allowStateLoss, because the dialog
462             // dispatches this asynchronously so we can receive the call
463             // after the activity is paused.  Worst case, when the user comes
464             // back to the activity they see the dialog again.
465             dismissInternal(true);
466         }
467     }
468 
469     @Override
onActivityCreated(Bundle savedInstanceState)470     public void onActivityCreated(Bundle savedInstanceState) {
471         super.onActivityCreated(savedInstanceState);
472 
473         if (!mShowsDialog) {
474             return;
475         }
476 
477         View view = getView();
478         if (view != null) {
479             if (view.getParent() != null) {
480                 throw new IllegalStateException(
481                         "DialogFragment can not be attached to a container view");
482             }
483             mDialog.setContentView(view);
484         }
485         final Activity activity = getActivity();
486         if (activity != null) {
487             mDialog.setOwnerActivity(activity);
488         }
489         mDialog.setCancelable(mCancelable);
490         if (!mDialog.takeCancelAndDismissListeners("DialogFragment", this, this)) {
491             throw new IllegalStateException(
492                     "You can not set Dialog's OnCancelListener or OnDismissListener");
493         }
494         if (savedInstanceState != null) {
495             Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
496             if (dialogState != null) {
497                 mDialog.onRestoreInstanceState(dialogState);
498             }
499         }
500     }
501 
502     @Override
onStart()503     public void onStart() {
504         super.onStart();
505         if (mDialog != null) {
506             mViewDestroyed = false;
507             mDialog.show();
508         }
509     }
510 
511     @Override
onSaveInstanceState(Bundle outState)512     public void onSaveInstanceState(Bundle outState) {
513         super.onSaveInstanceState(outState);
514         if (mDialog != null) {
515             Bundle dialogState = mDialog.onSaveInstanceState();
516             if (dialogState != null) {
517                 outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
518             }
519         }
520         if (mStyle != STYLE_NORMAL) {
521             outState.putInt(SAVED_STYLE, mStyle);
522         }
523         if (mTheme != 0) {
524             outState.putInt(SAVED_THEME, mTheme);
525         }
526         if (!mCancelable) {
527             outState.putBoolean(SAVED_CANCELABLE, mCancelable);
528         }
529         if (!mShowsDialog) {
530             outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
531         }
532         if (mBackStackId != -1) {
533             outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
534         }
535     }
536 
537     @Override
onStop()538     public void onStop() {
539         super.onStop();
540         if (mDialog != null) {
541             mDialog.hide();
542         }
543     }
544 
545     /**
546      * Remove dialog.
547      */
548     @Override
onDestroyView()549     public void onDestroyView() {
550         super.onDestroyView();
551         if (mDialog != null) {
552             // Set removed here because this dismissal is just to hide
553             // the dialog -- we don't want this to cause the fragment to
554             // actually be removed.
555             mViewDestroyed = true;
556             mDialog.dismiss();
557             mDialog = null;
558         }
559     }
560 
561     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)562     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
563         super.dump(prefix, fd, writer, args);
564         writer.print(prefix); writer.println("DialogFragment:");
565         writer.print(prefix); writer.print("  mStyle="); writer.print(mStyle);
566                 writer.print(" mTheme=0x"); writer.println(Integer.toHexString(mTheme));
567         writer.print(prefix); writer.print("  mCancelable="); writer.print(mCancelable);
568                 writer.print(" mShowsDialog="); writer.print(mShowsDialog);
569                 writer.print(" mBackStackId="); writer.println(mBackStackId);
570         writer.print(prefix); writer.print("  mDialog="); writer.println(mDialog);
571         writer.print(prefix); writer.print("  mViewDestroyed="); writer.print(mViewDestroyed);
572                 writer.print(" mDismissed="); writer.print(mDismissed);
573                 writer.print(" mShownByMe="); writer.println(mShownByMe);
574     }
575 }
576