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.content.Context; 20 import android.content.DialogInterface; 21 import android.os.Bundle; 22 import android.view.LayoutInflater; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.view.Window; 26 import android.view.WindowManager; 27 28 import java.io.FileDescriptor; 29 import 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 public class DialogFragment extends Fragment 141 implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { 142 143 /** 144 * Style for {@link #setStyle(int, int)}: a basic, 145 * normal dialog. 146 */ 147 public static final int STYLE_NORMAL = 0; 148 149 /** 150 * Style for {@link #setStyle(int, int)}: don't include 151 * a title area. 152 */ 153 public static final int STYLE_NO_TITLE = 1; 154 155 /** 156 * Style for {@link #setStyle(int, int)}: don't draw 157 * any frame at all; the view hierarchy returned by {@link #onCreateView} 158 * is entirely responsible for drawing the dialog. 159 */ 160 public static final int STYLE_NO_FRAME = 2; 161 162 /** 163 * Style for {@link #setStyle(int, int)}: like 164 * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog. 165 * The user can not touch it, and its window will not receive input focus. 166 */ 167 public static final int STYLE_NO_INPUT = 3; 168 169 private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState"; 170 private static final String SAVED_STYLE = "android:style"; 171 private static final String SAVED_THEME = "android:theme"; 172 private static final String SAVED_CANCELABLE = "android:cancelable"; 173 private static final String SAVED_SHOWS_DIALOG = "android:showsDialog"; 174 private static final String SAVED_BACK_STACK_ID = "android:backStackId"; 175 176 int mStyle = STYLE_NORMAL; 177 int mTheme = 0; 178 boolean mCancelable = true; 179 boolean mShowsDialog = true; 180 int mBackStackId = -1; 181 182 Dialog mDialog; 183 boolean mViewDestroyed; 184 boolean mDismissed; 185 boolean mShownByMe; 186 DialogFragment()187 public DialogFragment() { 188 } 189 190 /** 191 * Call to customize the basic appearance and behavior of the 192 * fragment's dialog. This can be used for some common dialog behaviors, 193 * taking care of selecting flags, theme, and other options for you. The 194 * same effect can be achieve by manually setting Dialog and Window 195 * attributes yourself. Calling this after the fragment's Dialog is 196 * created will have no effect. 197 * 198 * @param style Selects a standard style: may be {@link #STYLE_NORMAL}, 199 * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or 200 * {@link #STYLE_NO_INPUT}. 201 * @param theme Optional custom theme. If 0, an appropriate theme (based 202 * on the style) will be selected for you. 203 */ setStyle(int style, int theme)204 public void setStyle(int style, int theme) { 205 mStyle = style; 206 if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { 207 mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame; 208 } 209 if (theme != 0) { 210 mTheme = theme; 211 } 212 } 213 214 /** 215 * Display the dialog, adding the fragment to the given FragmentManager. This 216 * is a convenience for explicitly creating a transaction, adding the 217 * fragment to it with the given tag, and committing it. This does 218 * <em>not</em> add the transaction to the back stack. When the fragment 219 * is dismissed, a new transaction will be executed to remove it from 220 * the activity. 221 * @param manager The FragmentManager this fragment will be added to. 222 * @param tag The tag for this fragment, as per 223 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. 224 */ show(FragmentManager manager, String tag)225 public void show(FragmentManager manager, String tag) { 226 mDismissed = false; 227 mShownByMe = true; 228 FragmentTransaction ft = manager.beginTransaction(); 229 ft.add(this, tag); 230 ft.commit(); 231 } 232 233 /** {@hide} */ showAllowingStateLoss(FragmentManager manager, String tag)234 public void showAllowingStateLoss(FragmentManager manager, String tag) { 235 mDismissed = false; 236 mShownByMe = true; 237 FragmentTransaction ft = manager.beginTransaction(); 238 ft.add(this, tag); 239 ft.commitAllowingStateLoss(); 240 } 241 242 /** 243 * Display the dialog, adding the fragment using an existing transaction 244 * and then committing the transaction. 245 * @param transaction An existing transaction in which to add the fragment. 246 * @param tag The tag for this fragment, as per 247 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. 248 * @return Returns the identifier of the committed transaction, as per 249 * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. 250 */ show(FragmentTransaction transaction, String tag)251 public int show(FragmentTransaction transaction, String tag) { 252 mDismissed = false; 253 mShownByMe = true; 254 transaction.add(this, tag); 255 mViewDestroyed = false; 256 mBackStackId = transaction.commit(); 257 return mBackStackId; 258 } 259 260 /** 261 * Dismiss the fragment and its dialog. If the fragment was added to the 262 * back stack, all back stack state up to and including this entry will 263 * be popped. Otherwise, a new transaction will be committed to remove 264 * the fragment. 265 */ dismiss()266 public void dismiss() { 267 dismissInternal(false); 268 } 269 270 /** 271 * Version of {@link #dismiss()} that uses 272 * {@link FragmentTransaction#commitAllowingStateLoss() 273 * FragmentTransaction.commitAllowingStateLoss()}. See linked 274 * documentation for further details. 275 */ dismissAllowingStateLoss()276 public void dismissAllowingStateLoss() { 277 dismissInternal(true); 278 } 279 dismissInternal(boolean allowStateLoss)280 void dismissInternal(boolean allowStateLoss) { 281 if (mDismissed) { 282 return; 283 } 284 mDismissed = true; 285 mShownByMe = false; 286 if (mDialog != null) { 287 mDialog.dismiss(); 288 mDialog = null; 289 } 290 mViewDestroyed = true; 291 if (mBackStackId >= 0) { 292 getFragmentManager().popBackStack(mBackStackId, 293 FragmentManager.POP_BACK_STACK_INCLUSIVE); 294 mBackStackId = -1; 295 } else { 296 FragmentTransaction ft = getFragmentManager().beginTransaction(); 297 ft.remove(this); 298 if (allowStateLoss) { 299 ft.commitAllowingStateLoss(); 300 } else { 301 ft.commit(); 302 } 303 } 304 } 305 getDialog()306 public Dialog getDialog() { 307 return mDialog; 308 } 309 getTheme()310 public int getTheme() { 311 return mTheme; 312 } 313 314 /** 315 * Control whether the shown Dialog is cancelable. Use this instead of 316 * directly calling {@link Dialog#setCancelable(boolean) 317 * Dialog.setCancelable(boolean)}, because DialogFragment needs to change 318 * its behavior based on this. 319 * 320 * @param cancelable If true, the dialog is cancelable. The default 321 * is true. 322 */ setCancelable(boolean cancelable)323 public void setCancelable(boolean cancelable) { 324 mCancelable = cancelable; 325 if (mDialog != null) mDialog.setCancelable(cancelable); 326 } 327 328 /** 329 * Return the current value of {@link #setCancelable(boolean)}. 330 */ isCancelable()331 public boolean isCancelable() { 332 return mCancelable; 333 } 334 335 /** 336 * Controls whether this fragment should be shown in a dialog. If not 337 * set, no Dialog will be created in {@link #onActivityCreated(Bundle)}, 338 * and the fragment's view hierarchy will thus not be added to it. This 339 * allows you to instead use it as a normal fragment (embedded inside of 340 * its activity). 341 * 342 * <p>This is normally set for you based on whether the fragment is 343 * associated with a container view ID passed to 344 * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}. 345 * If the fragment was added with a container, setShowsDialog will be 346 * initialized to false; otherwise, it will be true. 347 * 348 * @param showsDialog If true, the fragment will be displayed in a Dialog. 349 * If false, no Dialog will be created and the fragment's view hierarchly 350 * left undisturbed. 351 */ setShowsDialog(boolean showsDialog)352 public void setShowsDialog(boolean showsDialog) { 353 mShowsDialog = showsDialog; 354 } 355 356 /** 357 * Return the current value of {@link #setShowsDialog(boolean)}. 358 */ getShowsDialog()359 public boolean getShowsDialog() { 360 return mShowsDialog; 361 } 362 363 @Override onAttach(Context context)364 public void onAttach(Context context) { 365 super.onAttach(context); 366 if (!mShownByMe) { 367 // If not explicitly shown through our API, take this as an 368 // indication that the dialog is no longer dismissed. 369 mDismissed = false; 370 } 371 } 372 373 @Override onDetach()374 public void onDetach() { 375 super.onDetach(); 376 if (!mShownByMe && !mDismissed) { 377 // The fragment was not shown by a direct call here, it is not 378 // dismissed, and now it is being detached... well, okay, thou 379 // art now dismissed. Have fun. 380 mDismissed = true; 381 } 382 } 383 384 @Override onCreate(Bundle savedInstanceState)385 public void onCreate(Bundle savedInstanceState) { 386 super.onCreate(savedInstanceState); 387 388 mShowsDialog = mContainerId == 0; 389 390 if (savedInstanceState != null) { 391 mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); 392 mTheme = savedInstanceState.getInt(SAVED_THEME, 0); 393 mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); 394 mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); 395 mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); 396 } 397 } 398 399 /** @hide */ 400 @Override getLayoutInflater(Bundle savedInstanceState)401 public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { 402 if (!mShowsDialog) { 403 return super.getLayoutInflater(savedInstanceState); 404 } 405 406 mDialog = onCreateDialog(savedInstanceState); 407 switch (mStyle) { 408 case STYLE_NO_INPUT: 409 mDialog.getWindow().addFlags( 410 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 411 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); 412 // fall through... 413 case STYLE_NO_FRAME: 414 case STYLE_NO_TITLE: 415 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 416 } 417 if (mDialog != null) { 418 return (LayoutInflater)mDialog.getContext().getSystemService( 419 Context.LAYOUT_INFLATER_SERVICE); 420 } 421 return (LayoutInflater) mHost.getContext().getSystemService( 422 Context.LAYOUT_INFLATER_SERVICE); 423 } 424 425 /** 426 * Override to build your own custom Dialog container. This is typically 427 * used to show an AlertDialog instead of a generic Dialog; when doing so, 428 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need 429 * to be implemented since the AlertDialog takes care of its own content. 430 * 431 * <p>This method will be called after {@link #onCreate(Bundle)} and 432 * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The 433 * default implementation simply instantiates and returns a {@link Dialog} 434 * class. 435 * 436 * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener 437 * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener 438 * Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em> 439 * To find out about these events, override {@link #onCancel(DialogInterface)} 440 * and {@link #onDismiss(DialogInterface)}.</p> 441 * 442 * @param savedInstanceState The last saved instance state of the Fragment, 443 * or null if this is a freshly created Fragment. 444 * 445 * @return Return a new Dialog instance to be displayed by the Fragment. 446 */ onCreateDialog(Bundle savedInstanceState)447 public Dialog onCreateDialog(Bundle savedInstanceState) { 448 return new Dialog(getActivity(), getTheme()); 449 } 450 onCancel(DialogInterface dialog)451 public void onCancel(DialogInterface dialog) { 452 } 453 onDismiss(DialogInterface dialog)454 public void onDismiss(DialogInterface dialog) { 455 if (!mViewDestroyed) { 456 // Note: we need to use allowStateLoss, because the dialog 457 // dispatches this asynchronously so we can receive the call 458 // after the activity is paused. Worst case, when the user comes 459 // back to the activity they see the dialog again. 460 dismissInternal(true); 461 } 462 } 463 464 @Override onActivityCreated(Bundle savedInstanceState)465 public void onActivityCreated(Bundle savedInstanceState) { 466 super.onActivityCreated(savedInstanceState); 467 468 if (!mShowsDialog) { 469 return; 470 } 471 472 View view = getView(); 473 if (view != null) { 474 if (view.getParent() != null) { 475 throw new IllegalStateException( 476 "DialogFragment can not be attached to a container view"); 477 } 478 mDialog.setContentView(view); 479 } 480 final Activity activity = getActivity(); 481 if (activity != null) { 482 mDialog.setOwnerActivity(activity); 483 } 484 mDialog.setCancelable(mCancelable); 485 if (!mDialog.takeCancelAndDismissListeners("DialogFragment", this, this)) { 486 throw new IllegalStateException( 487 "You can not set Dialog's OnCancelListener or OnDismissListener"); 488 } 489 if (savedInstanceState != null) { 490 Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG); 491 if (dialogState != null) { 492 mDialog.onRestoreInstanceState(dialogState); 493 } 494 } 495 } 496 497 @Override onStart()498 public void onStart() { 499 super.onStart(); 500 if (mDialog != null) { 501 mViewDestroyed = false; 502 mDialog.show(); 503 } 504 } 505 506 @Override onSaveInstanceState(Bundle outState)507 public void onSaveInstanceState(Bundle outState) { 508 super.onSaveInstanceState(outState); 509 if (mDialog != null) { 510 Bundle dialogState = mDialog.onSaveInstanceState(); 511 if (dialogState != null) { 512 outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); 513 } 514 } 515 if (mStyle != STYLE_NORMAL) { 516 outState.putInt(SAVED_STYLE, mStyle); 517 } 518 if (mTheme != 0) { 519 outState.putInt(SAVED_THEME, mTheme); 520 } 521 if (!mCancelable) { 522 outState.putBoolean(SAVED_CANCELABLE, mCancelable); 523 } 524 if (!mShowsDialog) { 525 outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); 526 } 527 if (mBackStackId != -1) { 528 outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); 529 } 530 } 531 532 @Override onStop()533 public void onStop() { 534 super.onStop(); 535 if (mDialog != null) { 536 mDialog.hide(); 537 } 538 } 539 540 /** 541 * Remove dialog. 542 */ 543 @Override onDestroyView()544 public void onDestroyView() { 545 super.onDestroyView(); 546 if (mDialog != null) { 547 // Set removed here because this dismissal is just to hide 548 // the dialog -- we don't want this to cause the fragment to 549 // actually be removed. 550 mViewDestroyed = true; 551 mDialog.dismiss(); 552 mDialog = null; 553 } 554 } 555 556 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)557 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 558 super.dump(prefix, fd, writer, args); 559 writer.print(prefix); writer.println("DialogFragment:"); 560 writer.print(prefix); writer.print(" mStyle="); writer.print(mStyle); 561 writer.print(" mTheme=0x"); writer.println(Integer.toHexString(mTheme)); 562 writer.print(prefix); writer.print(" mCancelable="); writer.print(mCancelable); 563 writer.print(" mShowsDialog="); writer.print(mShowsDialog); 564 writer.print(" mBackStackId="); writer.println(mBackStackId); 565 writer.print(prefix); writer.print(" mDialog="); writer.println(mDialog); 566 writer.print(prefix); writer.print(" mViewDestroyed="); writer.print(mViewDestroyed); 567 writer.print(" mDismissed="); writer.print(mDismissed); 568 writer.print(" mShownByMe="); writer.println(mShownByMe); 569 } 570 } 571