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