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.compat.annotation.UnsupportedAppUsage; 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 import java.io.FileDescriptor; 30 import java.io.PrintWriter; 31 32 /** 33 * A fragment that displays a dialog window, floating on top of its 34 * activity's window. This fragment contains a Dialog object, which it 35 * displays as appropriate based on the fragment's state. Control of 36 * the dialog (deciding when to show, hide, dismiss it) should be done through 37 * the API here, not with direct calls on the dialog. 38 * 39 * <p>Implementations should override this class and implement 40 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} to supply the 41 * content of the dialog. Alternatively, they can override 42 * {@link #onCreateDialog(Bundle)} to create an entirely custom dialog, such 43 * as an AlertDialog, with its own content. 44 * 45 * <p>Topics covered here: 46 * <ol> 47 * <li><a href="#Lifecycle">Lifecycle</a> 48 * <li><a href="#BasicDialog">Basic Dialog</a> 49 * <li><a href="#AlertDialog">Alert Dialog</a> 50 * <li><a href="#DialogOrEmbed">Selecting Between Dialog or Embedding</a> 51 * </ol> 52 * 53 * <a name="Lifecycle"></a> 54 * <h3>Lifecycle</h3> 55 * 56 * <p>DialogFragment does various things to keep the fragment's lifecycle 57 * driving it, instead of the Dialog. Note that dialogs are generally 58 * autonomous entities -- they are their own window, receiving their own 59 * input events, and often deciding on their own when to disappear (by 60 * receiving a back key event or the user clicking on a button). 61 * 62 * <p>DialogFragment needs to ensure that what is happening with the Fragment 63 * and Dialog states remains consistent. To do this, it watches for dismiss 64 * events from the dialog and takes care of removing its own state when they 65 * happen. This means you should use {@link #show(FragmentManager, String)} 66 * or {@link #show(FragmentTransaction, String)} to add an instance of 67 * DialogFragment to your UI, as these keep track of how DialogFragment should 68 * remove itself when the dialog is dismissed. 69 * 70 * <a name="BasicDialog"></a> 71 * <h3>Basic Dialog</h3> 72 * 73 * <p>The simplest use of DialogFragment is as a floating container for the 74 * fragment's view hierarchy. A simple implementation may look like this: 75 * 76 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java 77 * dialog} 78 * 79 * <p>An example showDialog() method on the Activity could be: 80 * 81 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialog.java 82 * add_dialog} 83 * 84 * <p>This removes any currently shown dialog, creates a new DialogFragment 85 * with an argument, and shows it as a new state on the back stack. When the 86 * transaction is popped, the current DialogFragment and its Dialog will be 87 * destroyed, and the previous one (if any) re-shown. Note that in this case 88 * DialogFragment will take care of popping the transaction of the Dialog 89 * is dismissed separately from it. 90 * 91 * <a name="AlertDialog"></a> 92 * <h3>Alert Dialog</h3> 93 * 94 * <p>Instead of (or in addition to) implementing {@link #onCreateView} to 95 * generate the view hierarchy inside of a dialog, you may implement 96 * {@link #onCreateDialog(Bundle)} to create your own custom Dialog object. 97 * 98 * <p>This is most useful for creating an {@link AlertDialog}, allowing you 99 * to display standard alerts to the user that are managed by a fragment. 100 * A simple example implementation of this is: 101 * 102 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java 103 * dialog} 104 * 105 * <p>The activity creating this fragment may have the following methods to 106 * show the dialog and receive results from it: 107 * 108 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentAlertDialog.java 109 * activity} 110 * 111 * <p>Note that in this case the fragment is not placed on the back stack, it 112 * is just added as an indefinitely running fragment. Because dialogs normally 113 * are modal, this will still operate as a back stack, since the dialog will 114 * capture user input until it is dismissed. When it is dismissed, DialogFragment 115 * will take care of removing itself from its fragment manager. 116 * 117 * <a name="DialogOrEmbed"></a> 118 * <h3>Selecting Between Dialog or Embedding</h3> 119 * 120 * <p>A DialogFragment can still optionally be used as a normal fragment, if 121 * desired. This is useful if you have a fragment that in some cases should 122 * be shown as a dialog and others embedded in a larger UI. This behavior 123 * will normally be automatically selected for you based on how you are using 124 * the fragment, but can be customized with {@link #setShowsDialog(boolean)}. 125 * 126 * <p>For example, here is a simple dialog fragment: 127 * 128 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java 129 * dialog} 130 * 131 * <p>An instance of this fragment can be created and shown as a dialog: 132 * 133 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java 134 * show_dialog} 135 * 136 * <p>It can also be added as content in a view hierarchy: 137 * 138 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java 139 * embed} 140 * 141 * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a> 142 * {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices 143 * and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>. 144 */ 145 @Deprecated 146 public class DialogFragment extends Fragment 147 implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { 148 149 /** 150 * Style for {@link #setStyle(int, int)}: a basic, 151 * normal dialog. 152 */ 153 public static final int STYLE_NORMAL = 0; 154 155 /** 156 * Style for {@link #setStyle(int, int)}: don't include 157 * a title area. 158 */ 159 public static final int STYLE_NO_TITLE = 1; 160 161 /** 162 * Style for {@link #setStyle(int, int)}: don't draw 163 * any frame at all; the view hierarchy returned by {@link #onCreateView} 164 * is entirely responsible for drawing the dialog. 165 */ 166 public static final int STYLE_NO_FRAME = 2; 167 168 /** 169 * Style for {@link #setStyle(int, int)}: like 170 * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog. 171 * The user can not touch it, and its window will not receive input focus. 172 */ 173 public static final int STYLE_NO_INPUT = 3; 174 175 private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState"; 176 private static final String SAVED_STYLE = "android:style"; 177 private static final String SAVED_THEME = "android:theme"; 178 private static final String SAVED_CANCELABLE = "android:cancelable"; 179 private static final String SAVED_SHOWS_DIALOG = "android:showsDialog"; 180 private static final String SAVED_BACK_STACK_ID = "android:backStackId"; 181 182 int mStyle = STYLE_NORMAL; 183 int mTheme = 0; 184 boolean mCancelable = true; 185 boolean mShowsDialog = true; 186 @UnsupportedAppUsage 187 int mBackStackId = -1; 188 189 Dialog mDialog; 190 @UnsupportedAppUsage 191 boolean mViewDestroyed; 192 @UnsupportedAppUsage 193 boolean mDismissed; 194 @UnsupportedAppUsage 195 boolean mShownByMe; 196 DialogFragment()197 public DialogFragment() { 198 } 199 200 /** 201 * Call to customize the basic appearance and behavior of the 202 * fragment's dialog. This can be used for some common dialog behaviors, 203 * taking care of selecting flags, theme, and other options for you. The 204 * same effect can be achieve by manually setting Dialog and Window 205 * attributes yourself. Calling this after the fragment's Dialog is 206 * created will have no effect. 207 * 208 * @param style Selects a standard style: may be {@link #STYLE_NORMAL}, 209 * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or 210 * {@link #STYLE_NO_INPUT}. 211 * @param theme Optional custom theme. If 0, an appropriate theme (based 212 * on the style) will be selected for you. 213 */ setStyle(int style, int theme)214 public void setStyle(int style, int theme) { 215 mStyle = style; 216 if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { 217 mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame; 218 } 219 if (theme != 0) { 220 mTheme = theme; 221 } 222 } 223 224 /** 225 * Display the dialog, adding the fragment to the given FragmentManager. This 226 * is a convenience for explicitly creating a transaction, adding the 227 * fragment to it with the given tag, and committing it. This does 228 * <em>not</em> add the transaction to the back stack. When the fragment 229 * is dismissed, a new transaction will be executed to remove it from 230 * the activity. 231 * @param manager The FragmentManager this fragment will be added to. 232 * @param tag The tag for this fragment, as per 233 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. 234 */ show(FragmentManager manager, String tag)235 public void show(FragmentManager manager, String tag) { 236 mDismissed = false; 237 mShownByMe = true; 238 FragmentTransaction ft = manager.beginTransaction(); 239 ft.add(this, tag); 240 ft.commit(); 241 } 242 243 /** {@hide} */ 244 @UnsupportedAppUsage showAllowingStateLoss(FragmentManager manager, String tag)245 public void showAllowingStateLoss(FragmentManager manager, String tag) { 246 mDismissed = false; 247 mShownByMe = true; 248 FragmentTransaction ft = manager.beginTransaction(); 249 ft.add(this, tag); 250 ft.commitAllowingStateLoss(); 251 } 252 253 /** 254 * Display the dialog, adding the fragment using an existing transaction 255 * and then committing the transaction. 256 * @param transaction An existing transaction in which to add the fragment. 257 * @param tag The tag for this fragment, as per 258 * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}. 259 * @return Returns the identifier of the committed transaction, as per 260 * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. 261 */ show(FragmentTransaction transaction, String tag)262 public int show(FragmentTransaction transaction, String tag) { 263 mDismissed = false; 264 mShownByMe = true; 265 transaction.add(this, tag); 266 mViewDestroyed = false; 267 mBackStackId = transaction.commit(); 268 return mBackStackId; 269 } 270 271 /** 272 * Dismiss the fragment and its dialog. If the fragment was added to the 273 * back stack, all back stack state up to and including this entry will 274 * be popped. Otherwise, a new transaction will be committed to remove 275 * the fragment. 276 */ dismiss()277 public void dismiss() { 278 dismissInternal(false); 279 } 280 281 /** 282 * Version of {@link #dismiss()} that uses 283 * {@link FragmentTransaction#commitAllowingStateLoss() 284 * FragmentTransaction.commitAllowingStateLoss()}. See linked 285 * documentation for further details. 286 */ dismissAllowingStateLoss()287 public void dismissAllowingStateLoss() { 288 dismissInternal(true); 289 } 290 dismissInternal(boolean allowStateLoss)291 void dismissInternal(boolean allowStateLoss) { 292 if (mDismissed) { 293 return; 294 } 295 mDismissed = true; 296 mShownByMe = false; 297 if (mDialog != null) { 298 mDialog.dismiss(); 299 mDialog = null; 300 } 301 mViewDestroyed = true; 302 if (mBackStackId >= 0) { 303 getFragmentManager().popBackStack(mBackStackId, 304 FragmentManager.POP_BACK_STACK_INCLUSIVE); 305 mBackStackId = -1; 306 } else { 307 FragmentTransaction ft = getFragmentManager().beginTransaction(); 308 ft.remove(this); 309 if (allowStateLoss) { 310 ft.commitAllowingStateLoss(); 311 } else { 312 ft.commit(); 313 } 314 } 315 } 316 getDialog()317 public Dialog getDialog() { 318 return mDialog; 319 } 320 getTheme()321 public int getTheme() { 322 return mTheme; 323 } 324 325 /** 326 * Control whether the shown Dialog is cancelable. Use this instead of 327 * directly calling {@link Dialog#setCancelable(boolean) 328 * Dialog.setCancelable(boolean)}, because DialogFragment needs to change 329 * its behavior based on this. 330 * 331 * @param cancelable If true, the dialog is cancelable. The default 332 * is true. 333 */ setCancelable(boolean cancelable)334 public void setCancelable(boolean cancelable) { 335 mCancelable = cancelable; 336 if (mDialog != null) mDialog.setCancelable(cancelable); 337 } 338 339 /** 340 * Return the current value of {@link #setCancelable(boolean)}. 341 */ isCancelable()342 public boolean isCancelable() { 343 return mCancelable; 344 } 345 346 /** 347 * Controls whether this fragment should be shown in a dialog. If not 348 * set, no Dialog will be created in {@link #onActivityCreated(Bundle)}, 349 * and the fragment's view hierarchy will thus not be added to it. This 350 * allows you to instead use it as a normal fragment (embedded inside of 351 * its activity). 352 * 353 * <p>This is normally set for you based on whether the fragment is 354 * associated with a container view ID passed to 355 * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}. 356 * If the fragment was added with a container, setShowsDialog will be 357 * initialized to false; otherwise, it will be true. 358 * 359 * @param showsDialog If true, the fragment will be displayed in a Dialog. 360 * If false, no Dialog will be created and the fragment's view hierarchly 361 * left undisturbed. 362 */ setShowsDialog(boolean showsDialog)363 public void setShowsDialog(boolean showsDialog) { 364 mShowsDialog = showsDialog; 365 } 366 367 /** 368 * Return the current value of {@link #setShowsDialog(boolean)}. 369 */ getShowsDialog()370 public boolean getShowsDialog() { 371 return mShowsDialog; 372 } 373 374 @Override onAttach(Context context)375 public void onAttach(Context context) { 376 super.onAttach(context); 377 if (!mShownByMe) { 378 // If not explicitly shown through our API, take this as an 379 // indication that the dialog is no longer dismissed. 380 mDismissed = false; 381 } 382 } 383 384 @Override onDetach()385 public void onDetach() { 386 super.onDetach(); 387 if (!mShownByMe && !mDismissed) { 388 // The fragment was not shown by a direct call here, it is not 389 // dismissed, and now it is being detached... well, okay, thou 390 // art now dismissed. Have fun. 391 mDismissed = true; 392 } 393 } 394 395 @Override onCreate(Bundle savedInstanceState)396 public void onCreate(Bundle savedInstanceState) { 397 super.onCreate(savedInstanceState); 398 399 mShowsDialog = mContainerId == 0; 400 401 if (savedInstanceState != null) { 402 mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); 403 mTheme = savedInstanceState.getInt(SAVED_THEME, 0); 404 mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); 405 mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); 406 mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); 407 } 408 } 409 410 /** @hide */ 411 @Override onGetLayoutInflater(Bundle savedInstanceState)412 public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) { 413 if (!mShowsDialog) { 414 return super.onGetLayoutInflater(savedInstanceState); 415 } 416 417 mDialog = onCreateDialog(savedInstanceState); 418 switch (mStyle) { 419 case STYLE_NO_INPUT: 420 mDialog.getWindow().addFlags( 421 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 422 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); 423 // fall through... 424 case STYLE_NO_FRAME: 425 case STYLE_NO_TITLE: 426 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 427 } 428 if (mDialog != null) { 429 return (LayoutInflater)mDialog.getContext().getSystemService( 430 Context.LAYOUT_INFLATER_SERVICE); 431 } 432 return (LayoutInflater) mHost.getContext().getSystemService( 433 Context.LAYOUT_INFLATER_SERVICE); 434 } 435 436 /** 437 * Override to build your own custom Dialog container. This is typically 438 * used to show an AlertDialog instead of a generic Dialog; when doing so, 439 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need 440 * to be implemented since the AlertDialog takes care of its own content. 441 * 442 * <p>This method will be called after {@link #onCreate(Bundle)} and 443 * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The 444 * default implementation simply instantiates and returns a {@link Dialog} 445 * class. 446 * 447 * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener 448 * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener 449 * Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em> 450 * To find out about these events, override {@link #onCancel(DialogInterface)} 451 * and {@link #onDismiss(DialogInterface)}.</p> 452 * 453 * @param savedInstanceState The last saved instance state of the Fragment, 454 * or null if this is a freshly created Fragment. 455 * 456 * @return Return a new Dialog instance to be displayed by the Fragment. 457 */ onCreateDialog(Bundle savedInstanceState)458 public Dialog onCreateDialog(Bundle savedInstanceState) { 459 return new Dialog(getActivity(), getTheme()); 460 } 461 onCancel(DialogInterface dialog)462 public void onCancel(DialogInterface dialog) { 463 } 464 onDismiss(DialogInterface dialog)465 public void onDismiss(DialogInterface dialog) { 466 if (!mViewDestroyed) { 467 // Note: we need to use allowStateLoss, because the dialog 468 // dispatches this asynchronously so we can receive the call 469 // after the activity is paused. Worst case, when the user comes 470 // back to the activity they see the dialog again. 471 dismissInternal(true); 472 } 473 } 474 475 @Override onActivityCreated(Bundle savedInstanceState)476 public void onActivityCreated(Bundle savedInstanceState) { 477 super.onActivityCreated(savedInstanceState); 478 479 if (!mShowsDialog) { 480 return; 481 } 482 483 View view = getView(); 484 if (view != null) { 485 if (view.getParent() != null) { 486 throw new IllegalStateException( 487 "DialogFragment can not be attached to a container view"); 488 } 489 mDialog.setContentView(view); 490 } 491 final Activity activity = getActivity(); 492 if (activity != null) { 493 mDialog.setOwnerActivity(activity); 494 } 495 mDialog.setCancelable(mCancelable); 496 if (!mDialog.takeCancelAndDismissListeners("DialogFragment", this, this)) { 497 throw new IllegalStateException( 498 "You can not set Dialog's OnCancelListener or OnDismissListener"); 499 } 500 if (savedInstanceState != null) { 501 Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG); 502 if (dialogState != null) { 503 mDialog.onRestoreInstanceState(dialogState); 504 } 505 } 506 } 507 508 @Override onStart()509 public void onStart() { 510 super.onStart(); 511 if (mDialog != null) { 512 mViewDestroyed = false; 513 mDialog.show(); 514 } 515 } 516 517 @Override onSaveInstanceState(Bundle outState)518 public void onSaveInstanceState(Bundle outState) { 519 super.onSaveInstanceState(outState); 520 if (mDialog != null) { 521 Bundle dialogState = mDialog.onSaveInstanceState(); 522 if (dialogState != null) { 523 outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); 524 } 525 } 526 if (mStyle != STYLE_NORMAL) { 527 outState.putInt(SAVED_STYLE, mStyle); 528 } 529 if (mTheme != 0) { 530 outState.putInt(SAVED_THEME, mTheme); 531 } 532 if (!mCancelable) { 533 outState.putBoolean(SAVED_CANCELABLE, mCancelable); 534 } 535 if (!mShowsDialog) { 536 outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); 537 } 538 if (mBackStackId != -1) { 539 outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); 540 } 541 } 542 543 @Override onStop()544 public void onStop() { 545 super.onStop(); 546 if (mDialog != null) { 547 mDialog.hide(); 548 } 549 } 550 551 /** 552 * Remove dialog. 553 */ 554 @Override onDestroyView()555 public void onDestroyView() { 556 super.onDestroyView(); 557 if (mDialog != null) { 558 // Set removed here because this dismissal is just to hide 559 // the dialog -- we don't want this to cause the fragment to 560 // actually be removed. 561 mViewDestroyed = true; 562 mDialog.dismiss(); 563 mDialog = null; 564 } 565 } 566 567 @Override dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)568 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 569 super.dump(prefix, fd, writer, args); 570 writer.print(prefix); writer.println("DialogFragment:"); 571 writer.print(prefix); writer.print(" mStyle="); writer.print(mStyle); 572 writer.print(" mTheme=0x"); writer.println(Integer.toHexString(mTheme)); 573 writer.print(prefix); writer.print(" mCancelable="); writer.print(mCancelable); 574 writer.print(" mShowsDialog="); writer.print(mShowsDialog); 575 writer.print(" mBackStackId="); writer.println(mBackStackId); 576 writer.print(prefix); writer.print(" mDialog="); writer.println(mDialog); 577 writer.print(prefix); writer.print(" mViewDestroyed="); writer.print(mViewDestroyed); 578 writer.print(" mDismissed="); writer.print(mDismissed); 579 writer.print(" mShownByMe="); writer.println(mShownByMe); 580 } 581 } 582