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