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
17package android.app;
18
19import android.content.Context;
20import android.content.DialogInterface;
21import android.os.Bundle;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.ViewGroup;
25import android.view.Window;
26import android.view.WindowManager;
27
28import java.io.FileDescriptor;
29import 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
145public 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
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     */
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     */
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} */
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     */
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     */
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     */
281    public void dismissAllowingStateLoss() {
282        dismissInternal(true);
283    }
284
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
311    public Dialog getDialog() {
312        return mDialog;
313    }
314
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     */
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     */
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     */
357    public void setShowsDialog(boolean showsDialog) {
358        mShowsDialog = showsDialog;
359    }
360
361    /**
362     * Return the current value of {@link #setShowsDialog(boolean)}.
363     */
364    public boolean getShowsDialog() {
365        return mShowsDialog;
366    }
367
368    @Override
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
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
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
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     */
452    public Dialog onCreateDialog(Bundle savedInstanceState) {
453        return new Dialog(getActivity(), getTheme());
454    }
455
456    public void onCancel(DialogInterface dialog) {
457    }
458
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
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
503    public void onStart() {
504        super.onStart();
505        if (mDialog != null) {
506            mViewDestroyed = false;
507            mDialog.show();
508        }
509    }
510
511    @Override
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
538    public void onStop() {
539        super.onStop();
540        if (mDialog != null) {
541            mDialog.hide();
542        }
543    }
544
545    /**
546     * Remove dialog.
547     */
548    @Override
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
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