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.compat.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 
DialogFragment()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      */
setStyle(int style, int theme)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      */
show(FragmentManager manager, String tag)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
showAllowingStateLoss(FragmentManager manager, String tag)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      */
show(FragmentTransaction transaction, String tag)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      */
dismiss()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      */
dismissAllowingStateLoss()287     public void dismissAllowingStateLoss() {
288         dismissInternal(true);
289     }
290 
dismissInternal(boolean allowStateLoss)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 
getDialog()317     public Dialog getDialog() {
318         return mDialog;
319     }
320 
getTheme()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      */
setCancelable(boolean cancelable)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      */
isCancelable()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      */
setShowsDialog(boolean showsDialog)363     public void setShowsDialog(boolean showsDialog) {
364         mShowsDialog = showsDialog;
365     }
366 
367     /**
368      * Return the current value of {@link #setShowsDialog(boolean)}.
369      */
getShowsDialog()370     public boolean getShowsDialog() {
371         return mShowsDialog;
372     }
373 
374     @Override
onAttach(Context context)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
onDetach()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
onCreate(Bundle savedInstanceState)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
onGetLayoutInflater(Bundle savedInstanceState)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      */
onCreateDialog(Bundle savedInstanceState)458     public Dialog onCreateDialog(Bundle savedInstanceState) {
459         return new Dialog(getActivity(), getTheme());
460     }
461 
onCancel(DialogInterface dialog)462     public void onCancel(DialogInterface dialog) {
463     }
464 
onDismiss(DialogInterface dialog)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
onActivityCreated(Bundle savedInstanceState)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
onStart()509     public void onStart() {
510         super.onStart();
511         if (mDialog != null) {
512             mViewDestroyed = false;
513             mDialog.show();
514         }
515     }
516 
517     @Override
onSaveInstanceState(Bundle outState)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
onStop()544     public void onStop() {
545         super.onStop();
546         if (mDialog != null) {
547             mDialog.hide();
548         }
549     }
550 
551     /**
552      * Remove dialog.
553      */
554     @Override
onDestroyView()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
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)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