• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.support.v4.app;
18 
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.os.Bundle;
24 import android.support.annotation.IntDef;
25 import android.support.annotation.NonNull;
26 import android.support.annotation.StyleRes;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.Window;
31 import android.view.WindowManager;
32 
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 
36 /**
37  * Static library support version of the framework's {@link android.app.DialogFragment}.
38  * Used to write apps that run on platforms prior to Android 3.0.  When running
39  * on Android 3.0 or above, this implementation is still used; it does not try
40  * to switch to the framework's implementation.  See the framework SDK
41  * documentation for a class overview.
42  */
43 public class DialogFragment extends Fragment
44         implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
45 
46     /** @hide */
47     @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
48     @Retention(RetentionPolicy.SOURCE)
49     private @interface DialogStyle {}
50 
51     /**
52      * Style for {@link #setStyle(int, int)}: a basic,
53      * normal dialog.
54      */
55     public static final int STYLE_NORMAL = 0;
56 
57     /**
58      * Style for {@link #setStyle(int, int)}: don't include
59      * a title area.
60      */
61     public static final int STYLE_NO_TITLE = 1;
62 
63     /**
64      * Style for {@link #setStyle(int, int)}: don't draw
65      * any frame at all; the view hierarchy returned by {@link #onCreateView}
66      * is entirely responsible for drawing the dialog.
67      */
68     public static final int STYLE_NO_FRAME = 2;
69 
70     /**
71      * Style for {@link #setStyle(int, int)}: like
72      * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
73      * The user can not touch it, and its window will not receive input focus.
74      */
75     public static final int STYLE_NO_INPUT = 3;
76 
77     private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
78     private static final String SAVED_STYLE = "android:style";
79     private static final String SAVED_THEME = "android:theme";
80     private static final String SAVED_CANCELABLE = "android:cancelable";
81     private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
82     private static final String SAVED_BACK_STACK_ID = "android:backStackId";
83 
84     int mStyle = STYLE_NORMAL;
85     int mTheme = 0;
86     boolean mCancelable = true;
87     boolean mShowsDialog = true;
88     int mBackStackId = -1;
89 
90     Dialog mDialog;
91     boolean mViewDestroyed;
92     boolean mDismissed;
93     boolean mShownByMe;
94 
DialogFragment()95     public DialogFragment() {
96     }
97 
98     /**
99      * Call to customize the basic appearance and behavior of the
100      * fragment's dialog.  This can be used for some common dialog behaviors,
101      * taking care of selecting flags, theme, and other options for you.  The
102      * same effect can be achieve by manually setting Dialog and Window
103      * attributes yourself.  Calling this after the fragment's Dialog is
104      * created will have no effect.
105      *
106      * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
107      * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
108      * {@link #STYLE_NO_INPUT}.
109      * @param theme Optional custom theme.  If 0, an appropriate theme (based
110      * on the style) will be selected for you.
111      */
setStyle(@ialogStyle int style, @StyleRes int theme)112     public void setStyle(@DialogStyle int style, @StyleRes int theme) {
113         mStyle = style;
114         if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
115             mTheme = android.R.style.Theme_Panel;
116         }
117         if (theme != 0) {
118             mTheme = theme;
119         }
120     }
121 
122     /**
123      * Display the dialog, adding the fragment to the given FragmentManager.  This
124      * is a convenience for explicitly creating a transaction, adding the
125      * fragment to it with the given tag, and committing it.  This does
126      * <em>not</em> add the transaction to the back stack.  When the fragment
127      * is dismissed, a new transaction will be executed to remove it from
128      * the activity.
129      * @param manager The FragmentManager this fragment will be added to.
130      * @param tag The tag for this fragment, as per
131      * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
132      */
show(FragmentManager manager, String tag)133     public void show(FragmentManager manager, String tag) {
134         mDismissed = false;
135         mShownByMe = true;
136         FragmentTransaction ft = manager.beginTransaction();
137         ft.add(this, tag);
138         ft.commit();
139     }
140 
141     /**
142      * Display the dialog, adding the fragment using an existing transaction
143      * and then committing the transaction.
144      * @param transaction An existing transaction in which to add the fragment.
145      * @param tag The tag for this fragment, as per
146      * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
147      * @return Returns the identifier of the committed transaction, as per
148      * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
149      */
show(FragmentTransaction transaction, String tag)150     public int show(FragmentTransaction transaction, String tag) {
151         mDismissed = false;
152         mShownByMe = true;
153         transaction.add(this, tag);
154         mViewDestroyed = false;
155         mBackStackId = transaction.commit();
156         return mBackStackId;
157     }
158 
159     /**
160      * Dismiss the fragment and its dialog.  If the fragment was added to the
161      * back stack, all back stack state up to and including this entry will
162      * be popped.  Otherwise, a new transaction will be committed to remove
163      * the fragment.
164      */
dismiss()165     public void dismiss() {
166         dismissInternal(false);
167     }
168 
169     /**
170      * Version of {@link #dismiss()} that uses
171      * {@link FragmentTransaction#commitAllowingStateLoss()
172      * FragmentTransaction.commitAllowingStateLoss()}. See linked
173      * documentation for further details.
174      */
dismissAllowingStateLoss()175     public void dismissAllowingStateLoss() {
176         dismissInternal(true);
177     }
178 
dismissInternal(boolean allowStateLoss)179     void dismissInternal(boolean allowStateLoss) {
180         if (mDismissed) {
181             return;
182         }
183         mDismissed = true;
184         mShownByMe = false;
185         if (mDialog != null) {
186             mDialog.dismiss();
187             mDialog = null;
188         }
189         mViewDestroyed = true;
190         if (mBackStackId >= 0) {
191             getFragmentManager().popBackStack(mBackStackId,
192                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
193             mBackStackId = -1;
194         } else {
195             FragmentTransaction ft = getFragmentManager().beginTransaction();
196             ft.remove(this);
197             if (allowStateLoss) {
198                 ft.commitAllowingStateLoss();
199             } else {
200                 ft.commit();
201             }
202         }
203     }
204 
getDialog()205     public Dialog getDialog() {
206         return mDialog;
207     }
208 
209     @StyleRes
getTheme()210     public int getTheme() {
211         return mTheme;
212     }
213 
214     /**
215      * Control whether the shown Dialog is cancelable.  Use this instead of
216      * directly calling {@link Dialog#setCancelable(boolean)
217      * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
218      * its behavior based on this.
219      *
220      * @param cancelable If true, the dialog is cancelable.  The default
221      * is true.
222      */
setCancelable(boolean cancelable)223     public void setCancelable(boolean cancelable) {
224         mCancelable = cancelable;
225         if (mDialog != null) mDialog.setCancelable(cancelable);
226     }
227 
228     /**
229      * Return the current value of {@link #setCancelable(boolean)}.
230      */
isCancelable()231     public boolean isCancelable() {
232         return mCancelable;
233     }
234 
235     /**
236      * Controls whether this fragment should be shown in a dialog.  If not
237      * set, no Dialog will be created in {@link #onActivityCreated(Bundle)},
238      * and the fragment's view hierarchy will thus not be added to it.  This
239      * allows you to instead use it as a normal fragment (embedded inside of
240      * its activity).
241      *
242      * <p>This is normally set for you based on whether the fragment is
243      * associated with a container view ID passed to
244      * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
245      * If the fragment was added with a container, setShowsDialog will be
246      * initialized to false; otherwise, it will be true.
247      *
248      * @param showsDialog If true, the fragment will be displayed in a Dialog.
249      * If false, no Dialog will be created and the fragment's view hierarchly
250      * left undisturbed.
251      */
setShowsDialog(boolean showsDialog)252     public void setShowsDialog(boolean showsDialog) {
253         mShowsDialog = showsDialog;
254     }
255 
256     /**
257      * Return the current value of {@link #setShowsDialog(boolean)}.
258      */
getShowsDialog()259     public boolean getShowsDialog() {
260         return mShowsDialog;
261     }
262 
263     @Override
onAttach(Activity activity)264     public void onAttach(Activity activity) {
265         super.onAttach(activity);
266         if (!mShownByMe) {
267             // If not explicitly shown through our API, take this as an
268             // indication that the dialog is no longer dismissed.
269             mDismissed = false;
270         }
271     }
272 
273     @Override
onDetach()274     public void onDetach() {
275         super.onDetach();
276         if (!mShownByMe && !mDismissed) {
277             // The fragment was not shown by a direct call here, it is not
278             // dismissed, and now it is being detached...  well, okay, thou
279             // art now dismissed.  Have fun.
280             mDismissed = true;
281         }
282     }
283 
284     @Override
onCreate(Bundle savedInstanceState)285     public void onCreate(Bundle savedInstanceState) {
286         super.onCreate(savedInstanceState);
287 
288         mShowsDialog = mContainerId == 0;
289 
290         if (savedInstanceState != null) {
291             mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
292             mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
293             mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
294             mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
295             mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
296         }
297 
298     }
299 
300     /** @hide */
301     @Override
getLayoutInflater(Bundle savedInstanceState)302     public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
303         if (!mShowsDialog) {
304             return super.getLayoutInflater(savedInstanceState);
305         }
306 
307         mDialog = onCreateDialog(savedInstanceState);
308         switch (mStyle) {
309             case STYLE_NO_INPUT:
310                 mDialog.getWindow().addFlags(
311                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
312                         WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
313                 // fall through...
314             case STYLE_NO_FRAME:
315             case STYLE_NO_TITLE:
316                 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
317         }
318         if (mDialog != null) {
319             return (LayoutInflater) mDialog.getContext().getSystemService(
320                     Context.LAYOUT_INFLATER_SERVICE);
321         }
322         return (LayoutInflater) mActivity.getSystemService(
323                 Context.LAYOUT_INFLATER_SERVICE);
324     }
325 
326     /**
327      * Override to build your own custom Dialog container.  This is typically
328      * used to show an AlertDialog instead of a generic Dialog; when doing so,
329      * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need
330      * to be implemented since the AlertDialog takes care of its own content.
331      *
332      * <p>This method will be called after {@link #onCreate(Bundle)} and
333      * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.  The
334      * default implementation simply instantiates and returns a {@link Dialog}
335      * class.
336      *
337      * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
338      * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
339      * Dialog.setOnDismissListener} callbacks.  You must not set them yourself.</em>
340      * To find out about these events, override {@link #onCancel(DialogInterface)}
341      * and {@link #onDismiss(DialogInterface)}.</p>
342      *
343      * @param savedInstanceState The last saved instance state of the Fragment,
344      * or null if this is a freshly created Fragment.
345      *
346      * @return Return a new Dialog instance to be displayed by the Fragment.
347      */
348     @NonNull
onCreateDialog(Bundle savedInstanceState)349     public Dialog onCreateDialog(Bundle savedInstanceState) {
350         return new Dialog(getActivity(), getTheme());
351     }
352 
onCancel(DialogInterface dialog)353     public void onCancel(DialogInterface dialog) {
354     }
355 
onDismiss(DialogInterface dialog)356     public void onDismiss(DialogInterface dialog) {
357         if (!mViewDestroyed) {
358             // Note: we need to use allowStateLoss, because the dialog
359             // dispatches this asynchronously so we can receive the call
360             // after the activity is paused.  Worst case, when the user comes
361             // back to the activity they see the dialog again.
362             dismissInternal(true);
363         }
364     }
365 
366     @Override
onActivityCreated(Bundle savedInstanceState)367     public void onActivityCreated(Bundle savedInstanceState) {
368         super.onActivityCreated(savedInstanceState);
369 
370         if (!mShowsDialog) {
371             return;
372         }
373 
374         View view = getView();
375         if (view != null) {
376             if (view.getParent() != null) {
377                 throw new IllegalStateException("DialogFragment can not be attached to a container view");
378             }
379             mDialog.setContentView(view);
380         }
381         mDialog.setOwnerActivity(getActivity());
382         mDialog.setCancelable(mCancelable);
383         mDialog.setOnCancelListener(this);
384         mDialog.setOnDismissListener(this);
385         if (savedInstanceState != null) {
386             Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
387             if (dialogState != null) {
388                 mDialog.onRestoreInstanceState(dialogState);
389             }
390         }
391     }
392 
393     @Override
onStart()394     public void onStart() {
395         super.onStart();
396         if (mDialog != null) {
397             mViewDestroyed = false;
398             mDialog.show();
399         }
400     }
401 
402     @Override
onSaveInstanceState(Bundle outState)403     public void onSaveInstanceState(Bundle outState) {
404         super.onSaveInstanceState(outState);
405         if (mDialog != null) {
406             Bundle dialogState = mDialog.onSaveInstanceState();
407             if (dialogState != null) {
408                 outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
409             }
410         }
411         if (mStyle != STYLE_NORMAL) {
412             outState.putInt(SAVED_STYLE, mStyle);
413         }
414         if (mTheme != 0) {
415             outState.putInt(SAVED_THEME, mTheme);
416         }
417         if (!mCancelable) {
418             outState.putBoolean(SAVED_CANCELABLE, mCancelable);
419         }
420         if (!mShowsDialog) {
421             outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
422         }
423         if (mBackStackId != -1) {
424             outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
425         }
426     }
427 
428     @Override
onStop()429     public void onStop() {
430         super.onStop();
431         if (mDialog != null) {
432             mDialog.hide();
433         }
434     }
435 
436     /**
437      * Remove dialog.
438      */
439     @Override
onDestroyView()440     public void onDestroyView() {
441         super.onDestroyView();
442         if (mDialog != null) {
443             // Set removed here because this dismissal is just to hide
444             // the dialog -- we don't want this to cause the fragment to
445             // actually be removed.
446             mViewDestroyed = true;
447             mDialog.dismiss();
448             mDialog = null;
449         }
450     }
451 }
452