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