[go: nahoru, domu]

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