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