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