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