1 /* 2 * Copyright (C) 2007 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.preference; 18 19 20 import android.annotation.CallSuper; 21 import android.annotation.DrawableRes; 22 import android.annotation.StringRes; 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.SharedPreferences; 28 import android.content.res.TypedArray; 29 import android.graphics.drawable.Drawable; 30 import android.os.Bundle; 31 import android.os.Parcel; 32 import android.os.Parcelable; 33 import android.text.TextUtils; 34 import android.util.AttributeSet; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.view.Window; 38 import android.view.WindowManager; 39 import android.widget.TextView; 40 41 /** 42 * A base class for {@link Preference} objects that are 43 * dialog-based. These preferences will, when clicked, open a dialog showing the 44 * actual preference controls. 45 * 46 * @attr ref android.R.styleable#DialogPreference_dialogTitle 47 * @attr ref android.R.styleable#DialogPreference_dialogMessage 48 * @attr ref android.R.styleable#DialogPreference_dialogIcon 49 * @attr ref android.R.styleable#DialogPreference_dialogLayout 50 * @attr ref android.R.styleable#DialogPreference_positiveButtonText 51 * @attr ref android.R.styleable#DialogPreference_negativeButtonText 52 */ 53 public abstract class DialogPreference extends Preference implements 54 DialogInterface.OnClickListener, DialogInterface.OnDismissListener, 55 PreferenceManager.OnActivityDestroyListener { 56 private AlertDialog.Builder mBuilder; 57 58 private CharSequence mDialogTitle; 59 private CharSequence mDialogMessage; 60 private Drawable mDialogIcon; 61 private CharSequence mPositiveButtonText; 62 private CharSequence mNegativeButtonText; 63 private int mDialogLayoutResId; 64 65 /** The dialog, if it is showing. */ 66 private Dialog mDialog; 67 68 /** Which button was clicked. */ 69 private int mWhichButtonClicked; 70 DialogPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)71 public DialogPreference( 72 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 73 super(context, attrs, defStyleAttr, defStyleRes); 74 75 final TypedArray a = context.obtainStyledAttributes(attrs, 76 com.android.internal.R.styleable.DialogPreference, defStyleAttr, defStyleRes); 77 mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle); 78 if (mDialogTitle == null) { 79 // Fallback on the regular title of the preference 80 // (the one that is seen in the list) 81 mDialogTitle = getTitle(); 82 } 83 mDialogMessage = a.getString(com.android.internal.R.styleable.DialogPreference_dialogMessage); 84 mDialogIcon = a.getDrawable(com.android.internal.R.styleable.DialogPreference_dialogIcon); 85 mPositiveButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_positiveButtonText); 86 mNegativeButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_negativeButtonText); 87 mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout, 88 mDialogLayoutResId); 89 a.recycle(); 90 } 91 DialogPreference(Context context, AttributeSet attrs, int defStyleAttr)92 public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { 93 this(context, attrs, defStyleAttr, 0); 94 } 95 DialogPreference(Context context, AttributeSet attrs)96 public DialogPreference(Context context, AttributeSet attrs) { 97 this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); 98 } 99 DialogPreference(Context context)100 public DialogPreference(Context context) { 101 this(context, null); 102 } 103 104 /** 105 * Sets the title of the dialog. This will be shown on subsequent dialogs. 106 * 107 * @param dialogTitle The title. 108 */ setDialogTitle(CharSequence dialogTitle)109 public void setDialogTitle(CharSequence dialogTitle) { 110 mDialogTitle = dialogTitle; 111 } 112 113 /** 114 * @see #setDialogTitle(CharSequence) 115 * @param dialogTitleResId The dialog title as a resource. 116 */ setDialogTitle(int dialogTitleResId)117 public void setDialogTitle(int dialogTitleResId) { 118 setDialogTitle(getContext().getString(dialogTitleResId)); 119 } 120 121 /** 122 * Returns the title to be shown on subsequent dialogs. 123 * @return The title. 124 */ getDialogTitle()125 public CharSequence getDialogTitle() { 126 return mDialogTitle; 127 } 128 129 /** 130 * Sets the message of the dialog. This will be shown on subsequent dialogs. 131 * <p> 132 * This message forms the content View of the dialog and conflicts with 133 * list-based dialogs, for example. If setting a custom View on a dialog via 134 * {@link #setDialogLayoutResource(int)}, include a text View with ID 135 * {@link android.R.id#message} and it will be populated with this message. 136 * 137 * @param dialogMessage The message. 138 */ setDialogMessage(CharSequence dialogMessage)139 public void setDialogMessage(CharSequence dialogMessage) { 140 mDialogMessage = dialogMessage; 141 } 142 143 /** 144 * @see #setDialogMessage(CharSequence) 145 * @param dialogMessageResId The dialog message as a resource. 146 */ setDialogMessage(int dialogMessageResId)147 public void setDialogMessage(int dialogMessageResId) { 148 setDialogMessage(getContext().getString(dialogMessageResId)); 149 } 150 151 /** 152 * Returns the message to be shown on subsequent dialogs. 153 * @return The message. 154 */ getDialogMessage()155 public CharSequence getDialogMessage() { 156 return mDialogMessage; 157 } 158 159 /** 160 * Sets the icon of the dialog. This will be shown on subsequent dialogs. 161 * 162 * @param dialogIcon The icon, as a {@link Drawable}. 163 */ setDialogIcon(Drawable dialogIcon)164 public void setDialogIcon(Drawable dialogIcon) { 165 mDialogIcon = dialogIcon; 166 } 167 168 /** 169 * Sets the icon (resource ID) of the dialog. This will be shown on 170 * subsequent dialogs. 171 * 172 * @param dialogIconRes The icon, as a resource ID. 173 */ setDialogIcon(@rawableRes int dialogIconRes)174 public void setDialogIcon(@DrawableRes int dialogIconRes) { 175 mDialogIcon = getContext().getDrawable(dialogIconRes); 176 } 177 178 /** 179 * Returns the icon to be shown on subsequent dialogs. 180 * @return The icon, as a {@link Drawable}. 181 */ getDialogIcon()182 public Drawable getDialogIcon() { 183 return mDialogIcon; 184 } 185 186 /** 187 * Sets the text of the positive button of the dialog. This will be shown on 188 * subsequent dialogs. 189 * 190 * @param positiveButtonText The text of the positive button. 191 */ setPositiveButtonText(CharSequence positiveButtonText)192 public void setPositiveButtonText(CharSequence positiveButtonText) { 193 mPositiveButtonText = positiveButtonText; 194 } 195 196 /** 197 * @see #setPositiveButtonText(CharSequence) 198 * @param positiveButtonTextResId The positive button text as a resource. 199 */ setPositiveButtonText(@tringRes int positiveButtonTextResId)200 public void setPositiveButtonText(@StringRes int positiveButtonTextResId) { 201 setPositiveButtonText(getContext().getString(positiveButtonTextResId)); 202 } 203 204 /** 205 * Returns the text of the positive button to be shown on subsequent 206 * dialogs. 207 * 208 * @return The text of the positive button. 209 */ getPositiveButtonText()210 public CharSequence getPositiveButtonText() { 211 return mPositiveButtonText; 212 } 213 214 /** 215 * Sets the text of the negative button of the dialog. This will be shown on 216 * subsequent dialogs. 217 * 218 * @param negativeButtonText The text of the negative button. 219 */ setNegativeButtonText(CharSequence negativeButtonText)220 public void setNegativeButtonText(CharSequence negativeButtonText) { 221 mNegativeButtonText = negativeButtonText; 222 } 223 224 /** 225 * @see #setNegativeButtonText(CharSequence) 226 * @param negativeButtonTextResId The negative button text as a resource. 227 */ setNegativeButtonText(@tringRes int negativeButtonTextResId)228 public void setNegativeButtonText(@StringRes int negativeButtonTextResId) { 229 setNegativeButtonText(getContext().getString(negativeButtonTextResId)); 230 } 231 232 /** 233 * Returns the text of the negative button to be shown on subsequent 234 * dialogs. 235 * 236 * @return The text of the negative button. 237 */ getNegativeButtonText()238 public CharSequence getNegativeButtonText() { 239 return mNegativeButtonText; 240 } 241 242 /** 243 * Sets the layout resource that is inflated as the {@link View} to be shown 244 * as the content View of subsequent dialogs. 245 * 246 * @param dialogLayoutResId The layout resource ID to be inflated. 247 * @see #setDialogMessage(CharSequence) 248 */ setDialogLayoutResource(int dialogLayoutResId)249 public void setDialogLayoutResource(int dialogLayoutResId) { 250 mDialogLayoutResId = dialogLayoutResId; 251 } 252 253 /** 254 * Returns the layout resource that is used as the content View for 255 * subsequent dialogs. 256 * 257 * @return The layout resource. 258 */ getDialogLayoutResource()259 public int getDialogLayoutResource() { 260 return mDialogLayoutResId; 261 } 262 263 /** 264 * Prepares the dialog builder to be shown when the preference is clicked. 265 * Use this to set custom properties on the dialog. 266 * <p> 267 * Do not {@link AlertDialog.Builder#create()} or 268 * {@link AlertDialog.Builder#show()}. 269 */ onPrepareDialogBuilder(AlertDialog.Builder builder)270 protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { 271 } 272 273 @Override onClick()274 protected void onClick() { 275 if (mDialog != null && mDialog.isShowing()) return; 276 277 showDialog(null); 278 } 279 280 /** 281 * Shows the dialog associated with this Preference. This is normally initiated 282 * automatically on clicking on the preference. Call this method if you need to 283 * show the dialog on some other event. 284 * 285 * @param state Optional instance state to restore on the dialog 286 */ showDialog(Bundle state)287 protected void showDialog(Bundle state) { 288 Context context = getContext(); 289 290 mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; 291 292 mBuilder = new AlertDialog.Builder(context) 293 .setTitle(mDialogTitle) 294 .setIcon(mDialogIcon) 295 .setPositiveButton(mPositiveButtonText, this) 296 .setNegativeButton(mNegativeButtonText, this); 297 298 View contentView = onCreateDialogView(); 299 if (contentView != null) { 300 onBindDialogView(contentView); 301 mBuilder.setView(contentView); 302 } else { 303 mBuilder.setMessage(mDialogMessage); 304 } 305 306 onPrepareDialogBuilder(mBuilder); 307 308 getPreferenceManager().registerOnActivityDestroyListener(this); 309 310 // Create the dialog 311 final Dialog dialog = mDialog = mBuilder.create(); 312 if (state != null) { 313 dialog.onRestoreInstanceState(state); 314 } 315 if (needInputMethod()) { 316 requestInputMethod(dialog); 317 } 318 dialog.setOnDismissListener(this); 319 dialog.show(); 320 } 321 322 /** 323 * Returns whether the preference needs to display a soft input method when the dialog 324 * is displayed. Default is false. Subclasses should override this method if they need 325 * the soft input method brought up automatically. 326 * @hide 327 */ needInputMethod()328 protected boolean needInputMethod() { 329 return false; 330 } 331 332 /** 333 * Sets the required flags on the dialog window to enable input method window to show up. 334 */ requestInputMethod(Dialog dialog)335 private void requestInputMethod(Dialog dialog) { 336 Window window = dialog.getWindow(); 337 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 338 } 339 340 /** 341 * Creates the content view for the dialog (if a custom content view is 342 * required). By default, it inflates the dialog layout resource if it is 343 * set. 344 * 345 * @return The content View for the dialog. 346 * @see #setLayoutResource(int) 347 */ onCreateDialogView()348 protected View onCreateDialogView() { 349 if (mDialogLayoutResId == 0) { 350 return null; 351 } 352 353 LayoutInflater inflater = LayoutInflater.from(mBuilder.getContext()); 354 return inflater.inflate(mDialogLayoutResId, null); 355 } 356 357 /** 358 * Binds views in the content View of the dialog to data. 359 * <p> 360 * Make sure to call through to the superclass implementation. 361 * 362 * @param view The content View of the dialog, if it is custom. 363 */ 364 @CallSuper onBindDialogView(View view)365 protected void onBindDialogView(View view) { 366 View dialogMessageView = view.findViewById(com.android.internal.R.id.message); 367 368 if (dialogMessageView != null) { 369 final CharSequence message = getDialogMessage(); 370 int newVisibility = View.GONE; 371 372 if (!TextUtils.isEmpty(message)) { 373 if (dialogMessageView instanceof TextView) { 374 ((TextView) dialogMessageView).setText(message); 375 } 376 377 newVisibility = View.VISIBLE; 378 } 379 380 if (dialogMessageView.getVisibility() != newVisibility) { 381 dialogMessageView.setVisibility(newVisibility); 382 } 383 } 384 } 385 onClick(DialogInterface dialog, int which)386 public void onClick(DialogInterface dialog, int which) { 387 mWhichButtonClicked = which; 388 } 389 onDismiss(DialogInterface dialog)390 public void onDismiss(DialogInterface dialog) { 391 392 getPreferenceManager().unregisterOnActivityDestroyListener(this); 393 394 mDialog = null; 395 onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE); 396 } 397 398 /** 399 * Called when the dialog is dismissed and should be used to save data to 400 * the {@link SharedPreferences}. 401 * 402 * @param positiveResult Whether the positive button was clicked (true), or 403 * the negative button was clicked or the dialog was canceled (false). 404 */ onDialogClosed(boolean positiveResult)405 protected void onDialogClosed(boolean positiveResult) { 406 } 407 408 /** 409 * Gets the dialog that is shown by this preference. 410 * 411 * @return The dialog, or null if a dialog is not being shown. 412 */ getDialog()413 public Dialog getDialog() { 414 return mDialog; 415 } 416 417 /** 418 * {@inheritDoc} 419 */ onActivityDestroy()420 public void onActivityDestroy() { 421 422 if (mDialog == null || !mDialog.isShowing()) { 423 return; 424 } 425 426 mDialog.dismiss(); 427 } 428 429 @Override onSaveInstanceState()430 protected Parcelable onSaveInstanceState() { 431 final Parcelable superState = super.onSaveInstanceState(); 432 if (mDialog == null || !mDialog.isShowing()) { 433 return superState; 434 } 435 436 final SavedState myState = new SavedState(superState); 437 myState.isDialogShowing = true; 438 myState.dialogBundle = mDialog.onSaveInstanceState(); 439 return myState; 440 } 441 442 @Override onRestoreInstanceState(Parcelable state)443 protected void onRestoreInstanceState(Parcelable state) { 444 if (state == null || !state.getClass().equals(SavedState.class)) { 445 // Didn't save state for us in onSaveInstanceState 446 super.onRestoreInstanceState(state); 447 return; 448 } 449 450 SavedState myState = (SavedState) state; 451 super.onRestoreInstanceState(myState.getSuperState()); 452 if (myState.isDialogShowing) { 453 showDialog(myState.dialogBundle); 454 } 455 } 456 457 private static class SavedState extends BaseSavedState { 458 boolean isDialogShowing; 459 Bundle dialogBundle; 460 SavedState(Parcel source)461 public SavedState(Parcel source) { 462 super(source); 463 isDialogShowing = source.readInt() == 1; 464 dialogBundle = source.readBundle(); 465 } 466 467 @Override writeToParcel(Parcel dest, int flags)468 public void writeToParcel(Parcel dest, int flags) { 469 super.writeToParcel(dest, flags); 470 dest.writeInt(isDialogShowing ? 1 : 0); 471 dest.writeBundle(dialogBundle); 472 } 473 SavedState(Parcelable superState)474 public SavedState(Parcelable superState) { 475 super(superState); 476 } 477 478 public static final Parcelable.Creator<SavedState> CREATOR = 479 new Parcelable.Creator<SavedState>() { 480 public SavedState createFromParcel(Parcel in) { 481 return new SavedState(in); 482 } 483 484 public SavedState[] newArray(int size) { 485 return new SavedState[size]; 486 } 487 }; 488 } 489 490 } 491