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