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