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