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