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