1 /* 2 * Copyright 2018 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 androidx.preference; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY; 20 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 21 22 import android.content.Context; 23 import android.content.Intent; 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.AbsSavedState; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.widget.ImageView; 36 import android.widget.TextView; 37 38 import androidx.annotation.CallSuper; 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.annotation.RestrictTo; 42 import androidx.core.content.ContextCompat; 43 import androidx.core.content.res.TypedArrayUtils; 44 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 45 import androidx.recyclerview.widget.RecyclerView; 46 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.Set; 50 51 /** 52 * Represents the basic Preference UI building 53 * block displayed by a {@link PreferenceFragmentCompat} in the form of a 54 * {@link RecyclerView}. This class provides data for the 55 * {@link android.view.View} to be displayed 56 * in the list and associates with a {@link SharedPreferences} to 57 * store/retrieve the preference data. 58 * <p> 59 * When specifying a preference hierarchy in XML, each element can point to a 60 * subclass of {@link Preference}, similar to the view hierarchy and layouts. 61 * <p> 62 * This class contains a {@code key} that will be used as the key into the 63 * {@link SharedPreferences}. It is up to the subclass to decide how to store 64 * the value. 65 * 66 * <div class="special reference"> 67 * <h3>Developer Guides</h3> 68 * <p>For information about building a settings UI with Preferences, 69 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> 70 * guide.</p> 71 * </div> 72 * 73 * @attr name android:icon 74 * @attr name android:key 75 * @attr name android:title 76 * @attr name android:summary 77 * @attr name android:order 78 * @attr name android:fragment 79 * @attr name android:layout 80 * @attr name android:widgetLayout 81 * @attr name android:enabled 82 * @attr name android:selectable 83 * @attr name android:dependency 84 * @attr name android:persistent 85 * @attr name android:defaultValue 86 * @attr name android:shouldDisableView 87 * @attr name android:singleLineTitle 88 * @attr name android:iconSpaceReserved 89 */ 90 public class Preference implements Comparable<Preference> { 91 /** 92 * Specify for {@link #setOrder(int)} if a specific order is not required. 93 */ 94 public static final int DEFAULT_ORDER = Integer.MAX_VALUE; 95 96 private Context mContext; 97 98 @Nullable 99 private PreferenceManager mPreferenceManager; 100 101 /** 102 * The data store that should be used by this Preference to store / retrieve data. If null then 103 * {@link PreferenceManager#getPreferenceDataStore()} needs to be checked. If that one is null 104 * too it means that we are using {@link android.content.SharedPreferences} to store the data. 105 */ 106 @Nullable 107 private PreferenceDataStore mPreferenceDataStore; 108 109 /** 110 * Set when added to hierarchy since we need a unique ID within that 111 * hierarchy. 112 */ 113 private long mId; 114 115 /** 116 * Set true temporarily to keep {@link #onAttachedToHierarchy(PreferenceManager)} from 117 * overwriting mId 118 */ 119 private boolean mHasId; 120 121 private OnPreferenceChangeListener mOnChangeListener; 122 private OnPreferenceClickListener mOnClickListener; 123 124 private int mOrder = DEFAULT_ORDER; 125 private int mViewId = 0; 126 private CharSequence mTitle; 127 private CharSequence mSummary; 128 /** 129 * mIconResId is overridden by mIcon, if mIcon is specified. 130 */ 131 private int mIconResId; 132 private Drawable mIcon; 133 private String mKey; 134 private Intent mIntent; 135 private String mFragment; 136 private Bundle mExtras; 137 private boolean mEnabled = true; 138 private boolean mSelectable = true; 139 private boolean mRequiresKey; 140 private boolean mPersistent = true; 141 private String mDependencyKey; 142 private Object mDefaultValue; 143 private boolean mDependencyMet = true; 144 private boolean mParentDependencyMet = true; 145 private boolean mVisible = true; 146 147 private boolean mAllowDividerAbove = true; 148 private boolean mAllowDividerBelow = true; 149 private boolean mHasSingleLineTitleAttr; 150 private boolean mSingleLineTitle = true; 151 private boolean mIconSpaceReserved; 152 153 /** 154 * @see #setShouldDisableView(boolean) 155 */ 156 private boolean mShouldDisableView = true; 157 158 private int mLayoutResId = R.layout.preference; 159 private int mWidgetLayoutResId; 160 161 private OnPreferenceChangeInternalListener mListener; 162 163 private List<Preference> mDependents; 164 private PreferenceGroup mParentGroup; 165 166 private boolean mWasDetached; 167 private boolean mBaseMethodCalled; 168 169 private final View.OnClickListener mClickListener = new View.OnClickListener() { 170 @Override 171 public void onClick(View v) { 172 performClick(v); 173 } 174 }; 175 176 /** 177 * Interface definition for a callback to be invoked when the value of this 178 * {@link Preference} has been changed by the user and is 179 * about to be set and/or persisted. This gives the client a chance 180 * to prevent setting and/or persisting the value. 181 */ 182 public interface OnPreferenceChangeListener { 183 /** 184 * Called when a Preference has been changed by the user. This is 185 * called before the state of the Preference is about to be updated and 186 * before the state is persisted. 187 * 188 * @param preference The changed Preference. 189 * @param newValue The new value of the Preference. 190 * @return True to update the state of the Preference with the new value. 191 */ onPreferenceChange(Preference preference, Object newValue)192 boolean onPreferenceChange(Preference preference, Object newValue); 193 } 194 195 /** 196 * Interface definition for a callback to be invoked when a {@link Preference} is 197 * clicked. 198 */ 199 public interface OnPreferenceClickListener { 200 /** 201 * Called when a Preference has been clicked. 202 * 203 * @param preference The Preference that was clicked. 204 * @return True if the click was handled. 205 */ onPreferenceClick(Preference preference)206 boolean onPreferenceClick(Preference preference); 207 } 208 209 /** 210 * Interface definition for a callback to be invoked when this 211 * {@link Preference} is changed or, if this is a group, there is an 212 * addition/removal of {@link Preference}(s). This is used internally. 213 */ 214 interface OnPreferenceChangeInternalListener { 215 /** 216 * Called when this Preference has changed. 217 * 218 * @param preference This preference. 219 */ onPreferenceChange(Preference preference)220 void onPreferenceChange(Preference preference); 221 222 /** 223 * Called when this group has added/removed {@link Preference}(s). 224 * 225 * @param preference This Preference. 226 */ onPreferenceHierarchyChange(Preference preference)227 void onPreferenceHierarchyChange(Preference preference); 228 229 /** 230 * Called when this preference has changed its visibility. 231 * 232 * @param preference This Preference. 233 */ onPreferenceVisibilityChange(Preference preference)234 void onPreferenceVisibilityChange(Preference preference); 235 } 236 237 /** 238 * Perform inflation from XML and apply a class-specific base style. This 239 * constructor of Preference allows subclasses to use their own base style 240 * when they are inflating. For example, a {@link CheckBoxPreference} 241 * constructor calls this version of the super class constructor and 242 * supplies {@code android.R.attr.checkBoxPreferenceStyle} for 243 * <var>defStyleAttr</var>. This allows the theme's checkbox preference 244 * style to modify all of the base preference attributes as well as the 245 * {@link CheckBoxPreference} class's attributes. 246 * 247 * @param context The Context this is associated with, through which it can 248 * access the current theme, resources, 249 * {@link android.content.SharedPreferences}, etc. 250 * @param attrs The attributes of the XML tag that is inflating the 251 * preference. 252 * @param defStyleAttr An attribute in the current theme that contains a 253 * reference to a style resource that supplies default values for 254 * the view. Can be 0 to not look for defaults. 255 * @param defStyleRes A resource identifier of a style resource that 256 * supplies default values for the view, used only if 257 * defStyleAttr is 0 or can not be found in the theme. Can be 0 258 * to not look for defaults. 259 * @see #Preference(Context, android.util.AttributeSet) 260 */ Preference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)261 public Preference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 262 mContext = context; 263 264 final TypedArray a = context.obtainStyledAttributes( 265 attrs, R.styleable.Preference, defStyleAttr, defStyleRes); 266 267 mIconResId = TypedArrayUtils.getResourceId(a, R.styleable.Preference_icon, 268 R.styleable.Preference_android_icon, 0); 269 270 mKey = TypedArrayUtils.getString(a, R.styleable.Preference_key, 271 R.styleable.Preference_android_key); 272 273 mTitle = TypedArrayUtils.getText(a, R.styleable.Preference_title, 274 R.styleable.Preference_android_title); 275 276 mSummary = TypedArrayUtils.getText(a, R.styleable.Preference_summary, 277 R.styleable.Preference_android_summary); 278 279 mOrder = TypedArrayUtils.getInt(a, R.styleable.Preference_order, 280 R.styleable.Preference_android_order, DEFAULT_ORDER); 281 282 mFragment = TypedArrayUtils.getString(a, R.styleable.Preference_fragment, 283 R.styleable.Preference_android_fragment); 284 285 mLayoutResId = TypedArrayUtils.getResourceId(a, R.styleable.Preference_layout, 286 R.styleable.Preference_android_layout, R.layout.preference); 287 288 mWidgetLayoutResId = TypedArrayUtils.getResourceId(a, R.styleable.Preference_widgetLayout, 289 R.styleable.Preference_android_widgetLayout, 0); 290 291 mEnabled = TypedArrayUtils.getBoolean(a, R.styleable.Preference_enabled, 292 R.styleable.Preference_android_enabled, true); 293 294 mSelectable = TypedArrayUtils.getBoolean(a, R.styleable.Preference_selectable, 295 R.styleable.Preference_android_selectable, true); 296 297 mPersistent = TypedArrayUtils.getBoolean(a, R.styleable.Preference_persistent, 298 R.styleable.Preference_android_persistent, true); 299 300 mDependencyKey = TypedArrayUtils.getString(a, R.styleable.Preference_dependency, 301 R.styleable.Preference_android_dependency); 302 303 mAllowDividerAbove = TypedArrayUtils.getBoolean(a, R.styleable.Preference_allowDividerAbove, 304 R.styleable.Preference_allowDividerAbove, mSelectable); 305 306 mAllowDividerBelow = TypedArrayUtils.getBoolean(a, R.styleable.Preference_allowDividerBelow, 307 R.styleable.Preference_allowDividerBelow, mSelectable); 308 309 if (a.hasValue(R.styleable.Preference_defaultValue)) { 310 mDefaultValue = onGetDefaultValue(a, R.styleable.Preference_defaultValue); 311 } else if (a.hasValue(R.styleable.Preference_android_defaultValue)) { 312 mDefaultValue = onGetDefaultValue(a, R.styleable.Preference_android_defaultValue); 313 } 314 315 mShouldDisableView = 316 TypedArrayUtils.getBoolean(a, R.styleable.Preference_shouldDisableView, 317 R.styleable.Preference_android_shouldDisableView, true); 318 319 mHasSingleLineTitleAttr = a.hasValue(R.styleable.Preference_singleLineTitle); 320 if (mHasSingleLineTitleAttr) { 321 mSingleLineTitle = TypedArrayUtils.getBoolean(a, R.styleable.Preference_singleLineTitle, 322 R.styleable.Preference_android_singleLineTitle, true); 323 } 324 325 mIconSpaceReserved = TypedArrayUtils.getBoolean(a, R.styleable.Preference_iconSpaceReserved, 326 R.styleable.Preference_android_iconSpaceReserved, false); 327 328 mVisible = TypedArrayUtils.getBoolean(a, R.styleable.Preference_isPreferenceVisible, 329 R.styleable.Preference_isPreferenceVisible, true); 330 331 a.recycle(); 332 } 333 334 /** 335 * Perform inflation from XML and apply a class-specific base style. This 336 * constructor of Preference allows subclasses to use their own base style 337 * when they are inflating. For example, a {@link CheckBoxPreference} 338 * constructor calls this version of the super class constructor and 339 * supplies {@code android.R.attr.checkBoxPreferenceStyle} for 340 * <var>defStyleAttr</var>. This allows the theme's checkbox preference 341 * style to modify all of the base preference attributes as well as the 342 * {@link CheckBoxPreference} class's attributes. 343 * 344 * @param context The Context this is associated with, through which it can 345 * access the current theme, resources, 346 * {@link android.content.SharedPreferences}, etc. 347 * @param attrs The attributes of the XML tag that is inflating the 348 * preference. 349 * @param defStyleAttr An attribute in the current theme that contains a 350 * reference to a style resource that supplies default values for 351 * the view. Can be 0 to not look for defaults. 352 * @see #Preference(Context, AttributeSet) 353 */ Preference(Context context, AttributeSet attrs, int defStyleAttr)354 public Preference(Context context, AttributeSet attrs, int defStyleAttr) { 355 this(context, attrs, defStyleAttr, 0); 356 } 357 358 /** 359 * Constructor that is called when inflating a Preference from XML. This is 360 * called when a Preference is being constructed from an XML file, supplying 361 * attributes that were specified in the XML file. This version uses a 362 * default style of 0, so the only attribute values applied are those in the 363 * Context's Theme and the given AttributeSet. 364 * 365 * @param context The Context this is associated with, through which it can 366 * access the current theme, resources, {@link android.content.SharedPreferences}, 367 * etc. 368 * @param attrs The attributes of the XML tag that is inflating the 369 * preference. 370 * @see #Preference(Context, AttributeSet, int) 371 */ Preference(Context context, AttributeSet attrs)372 public Preference(Context context, AttributeSet attrs) { 373 this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle, 374 android.R.attr.preferenceStyle)); 375 } 376 377 /** 378 * Constructor to create a Preference. 379 * 380 * @param context The Context in which to store Preference values. 381 */ Preference(Context context)382 public Preference(Context context) { 383 this(context, null); 384 } 385 386 /** 387 * Called when a Preference is being inflated and the default value 388 * attribute needs to be read. Since different Preference types have 389 * different value types, the subclass should get and return the default 390 * value which will be its value type. 391 * <p> 392 * For example, if the value type is String, the body of the method would 393 * proxy to {@link TypedArray#getString(int)}. 394 * 395 * @param a The set of attributes. 396 * @param index The index of the default value attribute. 397 * @return The default value of this preference type. 398 */ onGetDefaultValue(TypedArray a, int index)399 protected Object onGetDefaultValue(TypedArray a, int index) { 400 return null; 401 } 402 403 /** 404 * Sets an {@link Intent} to be used for 405 * {@link Context#startActivity(Intent)} when this Preference is clicked. 406 * 407 * @param intent The intent associated with this Preference. 408 */ setIntent(Intent intent)409 public void setIntent(Intent intent) { 410 mIntent = intent; 411 } 412 413 /** 414 * Return the {@link Intent} associated with this Preference. 415 * 416 * @return The {@link Intent} last set via {@link #setIntent(Intent)} or XML. 417 */ getIntent()418 public Intent getIntent() { 419 return mIntent; 420 } 421 422 /** 423 * Sets the class name of a fragment to be shown when this Preference is clicked. 424 * 425 * @param fragment The class name of the fragment associated with this Preference. 426 */ setFragment(String fragment)427 public void setFragment(String fragment) { 428 mFragment = fragment; 429 } 430 431 /** 432 * Return the fragment class name associated with this Preference. 433 * 434 * @return The fragment class name last set via {@link #setFragment} or XML. 435 */ getFragment()436 public String getFragment() { 437 return mFragment; 438 } 439 440 /** 441 * Sets a {@link PreferenceDataStore} to be used by this Preference instead of using 442 * {@link android.content.SharedPreferences}. 443 * 444 * <p>The data store will remain assigned even if the Preference is moved around the preference 445 * hierarchy. It will also override a data store propagated from the {@link PreferenceManager} 446 * that owns this Preference. 447 * 448 * @param dataStore the {@link PreferenceDataStore} to be used by this Preference 449 * @see PreferenceManager#setPreferenceDataStore(PreferenceDataStore) 450 */ setPreferenceDataStore(PreferenceDataStore dataStore)451 public void setPreferenceDataStore(PreferenceDataStore dataStore) { 452 mPreferenceDataStore = dataStore; 453 } 454 455 /** 456 * Returns {@link PreferenceDataStore} used by this Preference. Returns {@code null} if 457 * {@link android.content.SharedPreferences} is used instead. 458 * 459 * <p>By default preferences always use {@link android.content.SharedPreferences}. To make this 460 * preference to use the {@link PreferenceDataStore} you need to assign your implementation 461 * to the Preference itself via {@link #setPreferenceDataStore(PreferenceDataStore)} or to its 462 * {@link PreferenceManager} via 463 * {@link PreferenceManager#setPreferenceDataStore(PreferenceDataStore)}. 464 * 465 * @return the {@link PreferenceDataStore} used by this Preference or {@code null} if none 466 */ 467 @Nullable getPreferenceDataStore()468 public PreferenceDataStore getPreferenceDataStore() { 469 if (mPreferenceDataStore != null) { 470 return mPreferenceDataStore; 471 } else if (mPreferenceManager != null) { 472 return mPreferenceManager.getPreferenceDataStore(); 473 } 474 475 return null; 476 } 477 478 /** 479 * Return the extras Bundle object associated with this preference, creating 480 * a new Bundle if there currently isn't one. You can use this to get and 481 * set individual extra key/value pairs. 482 */ getExtras()483 public Bundle getExtras() { 484 if (mExtras == null) { 485 mExtras = new Bundle(); 486 } 487 return mExtras; 488 } 489 490 /** 491 * Return the extras Bundle object associated with this preference, 492 * returning null if there is not currently one. 493 */ peekExtras()494 public Bundle peekExtras() { 495 return mExtras; 496 } 497 498 /** 499 * Sets the layout resource that is inflated as the {@link View} to be shown 500 * for this Preference. In most cases, the default layout is sufficient for 501 * custom Preference objects and only the widget layout needs to be changed. 502 * <p> 503 * This layout should contain a {@link ViewGroup} with ID 504 * {@link android.R.id#widget_frame} to be the parent of the specific widget 505 * for this Preference. It should similarly contain 506 * {@link android.R.id#title} and {@link android.R.id#summary}. 507 * <p> 508 * It is an error to change the layout after adding the preference to a {@link PreferenceGroup} 509 * 510 * @param layoutResId The layout resource ID to be inflated and returned as 511 * a {@link View}. 512 * @see #setWidgetLayoutResource(int) 513 */ setLayoutResource(int layoutResId)514 public void setLayoutResource(int layoutResId) { 515 mLayoutResId = layoutResId; 516 } 517 518 /** 519 * Gets the layout resource that will be shown as the {@link View} for this Preference. 520 * 521 * @return The layout resource ID. 522 */ getLayoutResource()523 public final int getLayoutResource() { 524 return mLayoutResId; 525 } 526 527 /** 528 * Sets the layout for the controllable widget portion of this Preference. This 529 * is inflated into the main layout. For example, a {@link CheckBoxPreference} 530 * would specify a custom layout (consisting of just the CheckBox) here, 531 * instead of creating its own main layout. 532 * <p> 533 * It is an error to change the layout after adding the preference to a {@link PreferenceGroup} 534 * 535 * @param widgetLayoutResId The layout resource ID to be inflated into the 536 * main layout. 537 * @see #setLayoutResource(int) 538 */ setWidgetLayoutResource(int widgetLayoutResId)539 public void setWidgetLayoutResource(int widgetLayoutResId) { 540 mWidgetLayoutResId = widgetLayoutResId; 541 } 542 543 /** 544 * Gets the layout resource for the controllable widget portion of this Preference. 545 * 546 * @return The layout resource ID. 547 */ getWidgetLayoutResource()548 public final int getWidgetLayoutResource() { 549 return mWidgetLayoutResId; 550 } 551 552 /** 553 * Binds the created View to the data for this Preference. 554 * <p> 555 * This is a good place to grab references to custom Views in the layout and 556 * set properties on them. 557 * <p> 558 * Make sure to call through to the superclass's implementation. 559 * 560 * @param holder The ViewHolder that provides references to the views to fill in. These views 561 * will be recycled, so you should not hold a reference to them after this method 562 * returns. 563 */ onBindViewHolder(PreferenceViewHolder holder)564 public void onBindViewHolder(PreferenceViewHolder holder) { 565 holder.itemView.setOnClickListener(mClickListener); 566 holder.itemView.setId(mViewId); 567 568 final TextView titleView = (TextView) holder.findViewById(android.R.id.title); 569 if (titleView != null) { 570 final CharSequence title = getTitle(); 571 if (!TextUtils.isEmpty(title)) { 572 titleView.setText(title); 573 titleView.setVisibility(View.VISIBLE); 574 if (mHasSingleLineTitleAttr) { 575 titleView.setSingleLine(mSingleLineTitle); 576 } 577 } else { 578 titleView.setVisibility(View.GONE); 579 } 580 } 581 582 final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); 583 if (summaryView != null) { 584 final CharSequence summary = getSummary(); 585 if (!TextUtils.isEmpty(summary)) { 586 summaryView.setText(summary); 587 summaryView.setVisibility(View.VISIBLE); 588 } else { 589 summaryView.setVisibility(View.GONE); 590 } 591 } 592 593 final ImageView imageView = (ImageView) holder.findViewById(android.R.id.icon); 594 if (imageView != null) { 595 if (mIconResId != 0 || mIcon != null) { 596 if (mIcon == null) { 597 mIcon = ContextCompat.getDrawable(getContext(), mIconResId); 598 } 599 if (mIcon != null) { 600 imageView.setImageDrawable(mIcon); 601 } 602 } 603 if (mIcon != null) { 604 imageView.setVisibility(View.VISIBLE); 605 } else { 606 imageView.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE); 607 } 608 } 609 610 View imageFrame = holder.findViewById(R.id.icon_frame); 611 if (imageFrame == null) { 612 imageFrame = holder.findViewById(AndroidResources.ANDROID_R_ICON_FRAME); 613 } 614 if (imageFrame != null) { 615 if (mIcon != null) { 616 imageFrame.setVisibility(View.VISIBLE); 617 } else { 618 imageFrame.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE); 619 } 620 } 621 622 if (mShouldDisableView) { 623 setEnabledStateOnViews(holder.itemView, isEnabled()); 624 } else { 625 setEnabledStateOnViews(holder.itemView, true); 626 } 627 628 final boolean selectable = isSelectable(); 629 holder.itemView.setFocusable(selectable); 630 holder.itemView.setClickable(selectable); 631 632 holder.setDividerAllowedAbove(mAllowDividerAbove); 633 holder.setDividerAllowedBelow(mAllowDividerBelow); 634 } 635 636 /** 637 * Makes sure the view (and any children) get the enabled state changed. 638 */ setEnabledStateOnViews(View v, boolean enabled)639 private void setEnabledStateOnViews(View v, boolean enabled) { 640 v.setEnabled(enabled); 641 642 if (v instanceof ViewGroup) { 643 final ViewGroup vg = (ViewGroup) v; 644 for (int i = vg.getChildCount() - 1; i >= 0; i--) { 645 setEnabledStateOnViews(vg.getChildAt(i), enabled); 646 } 647 } 648 } 649 650 /** 651 * Sets the order of this Preference with respect to other 652 * Preference objects on the same level. If this is not specified, the 653 * default behavior is to sort alphabetically. The 654 * {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order 655 * Preference objects based on the order they appear in the XML. 656 * 657 * @param order The order for this Preference. A lower value will be shown 658 * first. Use {@link #DEFAULT_ORDER} to sort alphabetically or 659 * allow ordering from XML. 660 * @see PreferenceGroup#setOrderingAsAdded(boolean) 661 * @see #DEFAULT_ORDER 662 */ setOrder(int order)663 public void setOrder(int order) { 664 if (order != mOrder) { 665 mOrder = order; 666 667 // Reorder the list 668 notifyHierarchyChanged(); 669 } 670 } 671 672 /** 673 * Gets the order of this Preference with respect to other Preference objects 674 * on the same level. 675 * 676 * @return The order of this Preference. 677 * @see #setOrder(int) 678 */ getOrder()679 public int getOrder() { 680 return mOrder; 681 } 682 683 /** 684 * Set the ID that will be assigned to the overall View representing this 685 * preference, once bound. 686 * 687 * @see View#setId(int) 688 */ setViewId(int viewId)689 public void setViewId(int viewId) { 690 mViewId = viewId; 691 } 692 693 /** 694 * Sets the title for this Preference with a CharSequence. 695 * This title will be placed into the ID 696 * {@link android.R.id#title} within the View bound by 697 * {@link #onBindViewHolder(PreferenceViewHolder)}. 698 * 699 * @param title The title for this Preference. 700 */ setTitle(CharSequence title)701 public void setTitle(CharSequence title) { 702 if ((title == null && mTitle != null) || (title != null && !title.equals(mTitle))) { 703 mTitle = title; 704 notifyChanged(); 705 } 706 } 707 708 /** 709 * Sets the title for this Preference with a resource ID. 710 * 711 * @see #setTitle(CharSequence) 712 * @param titleResId The title as a resource ID. 713 */ setTitle(int titleResId)714 public void setTitle(int titleResId) { 715 setTitle(mContext.getString(titleResId)); 716 } 717 718 /** 719 * Returns the title of this Preference. 720 * 721 * @return The title. 722 * @see #setTitle(CharSequence) 723 */ getTitle()724 public CharSequence getTitle() { 725 return mTitle; 726 } 727 728 /** 729 * Sets the icon for this Preference with a Drawable. 730 * This icon will be placed into the ID 731 * {@link android.R.id#icon} within the View created by 732 * {@link #onBindViewHolder(PreferenceViewHolder)}. 733 * 734 * @param icon The optional icon for this Preference. 735 */ setIcon(Drawable icon)736 public void setIcon(Drawable icon) { 737 if ((icon == null && mIcon != null) || (icon != null && mIcon != icon)) { 738 mIcon = icon; 739 mIconResId = 0; 740 notifyChanged(); 741 } 742 } 743 744 /** 745 * Sets the icon for this Preference with a resource ID. 746 * 747 * @see #setIcon(Drawable) 748 * @param iconResId The icon as a resource ID. 749 */ setIcon(int iconResId)750 public void setIcon(int iconResId) { 751 setIcon(ContextCompat.getDrawable(mContext, iconResId)); 752 mIconResId = iconResId; 753 } 754 755 /** 756 * Returns the icon of this Preference. 757 * 758 * @return The icon. 759 * @see #setIcon(Drawable) 760 */ getIcon()761 public Drawable getIcon() { 762 if (mIcon == null && mIconResId != 0) { 763 mIcon = ContextCompat.getDrawable(mContext, mIconResId); 764 } 765 return mIcon; 766 } 767 768 /** 769 * Returns the summary of this Preference. 770 * 771 * @return The summary. 772 * @see #setSummary(CharSequence) 773 */ getSummary()774 public CharSequence getSummary() { 775 return mSummary; 776 } 777 778 /** 779 * Sets the summary for this Preference with a CharSequence. 780 * 781 * @param summary The summary for the preference. 782 */ setSummary(CharSequence summary)783 public void setSummary(CharSequence summary) { 784 if ((summary == null && mSummary != null) 785 || (summary != null && !summary.equals(mSummary))) { 786 mSummary = summary; 787 notifyChanged(); 788 } 789 } 790 791 /** 792 * Sets the summary for this Preference with a resource ID. 793 * 794 * @see #setSummary(CharSequence) 795 * @param summaryResId The summary as a resource. 796 */ setSummary(int summaryResId)797 public void setSummary(int summaryResId) { 798 setSummary(mContext.getString(summaryResId)); 799 } 800 801 /** 802 * Sets whether this Preference is enabled. If disabled, it will 803 * not handle clicks. 804 * 805 * @param enabled Set true to enable it. 806 */ setEnabled(boolean enabled)807 public void setEnabled(boolean enabled) { 808 if (mEnabled != enabled) { 809 mEnabled = enabled; 810 811 // Enabled state can change dependent preferences' states, so notify 812 notifyDependencyChange(shouldDisableDependents()); 813 814 notifyChanged(); 815 } 816 } 817 818 /** 819 * Checks whether this Preference should be enabled in the list. 820 * 821 * @return True if this Preference is enabled, false otherwise. 822 */ isEnabled()823 public boolean isEnabled() { 824 return mEnabled && mDependencyMet && mParentDependencyMet; 825 } 826 827 /** 828 * Sets whether this Preference is selectable. 829 * 830 * @param selectable Set true to make it selectable. 831 */ setSelectable(boolean selectable)832 public void setSelectable(boolean selectable) { 833 if (mSelectable != selectable) { 834 mSelectable = selectable; 835 notifyChanged(); 836 } 837 } 838 839 /** 840 * Checks whether this Preference should be selectable in the list. 841 * 842 * @return True if it is selectable, false otherwise. 843 */ isSelectable()844 public boolean isSelectable() { 845 return mSelectable; 846 } 847 848 /** 849 * Sets whether this Preference should disable its view when it gets 850 * disabled. 851 * <p> 852 * For example, set this and {@link #setEnabled(boolean)} to false for 853 * preferences that are only displaying information and 1) should not be 854 * clickable 2) should not have the view set to the disabled state. 855 * 856 * @param shouldDisableView Set true if this preference should disable its view 857 * when the preference is disabled. 858 */ setShouldDisableView(boolean shouldDisableView)859 public void setShouldDisableView(boolean shouldDisableView) { 860 mShouldDisableView = shouldDisableView; 861 notifyChanged(); 862 } 863 864 /** 865 * Checks whether this Preference should disable its view when it's action is disabled. 866 * @see #setShouldDisableView(boolean) 867 * @return True if it should disable the view. 868 */ getShouldDisableView()869 public boolean getShouldDisableView() { 870 return mShouldDisableView; 871 } 872 873 /** 874 * Sets whether this preference should be visible in the list. If false, it is excluded from 875 * the adapter, but can still be retrieved using 876 * {@link PreferenceFragmentCompat#findPreference(CharSequence)}. 877 * 878 * @param visible Set false if this preference should be hidden from the list. 879 * 880 * @attr ref R.styleable#Preference_isPreferenceVisible 881 */ setVisible(boolean visible)882 public final void setVisible(boolean visible) { 883 if (mVisible != visible) { 884 mVisible = visible; 885 if (mListener != null) { 886 mListener.onPreferenceVisibilityChange(this); 887 } 888 } 889 } 890 891 /** 892 * Checks whether this preference should be visible to the user in the list. 893 * @see #setVisible(boolean) 894 * @return True if this preference should be displayed. 895 */ isVisible()896 public final boolean isVisible() { 897 return mVisible; 898 } 899 900 /** 901 * Returns a unique ID for this Preference. This ID should be unique across all 902 * Preference objects in a hierarchy. 903 * 904 * @return A unique ID for this Preference. 905 */ getId()906 long getId() { 907 return mId; 908 } 909 910 /** 911 * Processes a click on the preference. This includes saving the value to 912 * the {@link android.content.SharedPreferences}. However, the overridden method should 913 * call {@link #callChangeListener(Object)} to make sure the client wants to 914 * update the preference's state with the new value. 915 */ onClick()916 protected void onClick() { 917 } 918 919 /** 920 * Sets the key for this Preference, which is used as a key to the {@link SharedPreferences} or 921 * {@link PreferenceDataStore}. This should be unique for the package. 922 * 923 * @param key The key for the preference. 924 */ setKey(String key)925 public void setKey(String key) { 926 mKey = key; 927 928 if (mRequiresKey && !hasKey()) { 929 requireKey(); 930 } 931 } 932 933 /** 934 * Gets the key for this Preference, which is also the key used for storing values into 935 * {@link SharedPreferences} or {@link PreferenceDataStore}. 936 * 937 * @return The key. 938 */ getKey()939 public String getKey() { 940 return mKey; 941 } 942 943 /** 944 * Checks whether the key is present, and if it isn't throws an exception. This should be called 945 * by subclasses that persist their preferences. 946 * 947 * @throws IllegalStateException If there is no key assigned. 948 */ requireKey()949 void requireKey() { 950 if (TextUtils.isEmpty(mKey)) { 951 throw new IllegalStateException("Preference does not have a key assigned."); 952 } 953 954 mRequiresKey = true; 955 } 956 957 /** 958 * Checks whether this Preference has a valid key. 959 * 960 * @return True if the key exists and is not a blank string, false otherwise. 961 */ hasKey()962 public boolean hasKey() { 963 return !TextUtils.isEmpty(mKey); 964 } 965 966 /** 967 * Checks whether this Preference is persistent. If it is, it stores its value(s) into 968 * the persistent {@link SharedPreferences} storage by default or into 969 * {@link PreferenceDataStore} if assigned. 970 * 971 * @return {@code true} if persistent 972 */ isPersistent()973 public boolean isPersistent() { 974 return mPersistent; 975 } 976 977 /** 978 * Checks whether, at the given time this method is called, this Preference should store/restore 979 * its value(s) into the {@link SharedPreferences} or into {@link PreferenceDataStore} if 980 * assigned. This, at minimum, checks whether this Preference is persistent and it currently has 981 * a key. Before you save/restore from the storage, check this first. 982 * 983 * @return {@code true} if it should persist the value 984 */ shouldPersist()985 protected boolean shouldPersist() { 986 return mPreferenceManager != null && isPersistent() && hasKey(); 987 } 988 989 /** 990 * Sets whether this Preference is persistent. When persistent, it stores its value(s) into 991 * the persistent {@link SharedPreferences} storage by default or into 992 * {@link PreferenceDataStore} if assigned. 993 * 994 * @param persistent set {@code true} if it should store its value(s) into the storage. 995 */ setPersistent(boolean persistent)996 public void setPersistent(boolean persistent) { 997 mPersistent = persistent; 998 } 999 1000 /** 1001 * Sets whether to constrain the title of this Preference to a single line instead of 1002 * letting it wrap onto multiple lines. 1003 * 1004 * @param singleLineTitle set {@code true} if the title should be constrained to one line 1005 * 1006 * @attr ref R.styleable#Preference_android_singleLineTitle 1007 */ setSingleLineTitle(boolean singleLineTitle)1008 public void setSingleLineTitle(boolean singleLineTitle) { 1009 mHasSingleLineTitleAttr = true; 1010 mSingleLineTitle = singleLineTitle; 1011 } 1012 1013 /** 1014 * Gets whether the title of this preference is constrained to a single line. 1015 * 1016 * @see #setSingleLineTitle(boolean) 1017 * @return {@code true} if the title of this preference is constrained to a single line 1018 * 1019 * @attr ref R.styleable#Preference_android_singleLineTitle 1020 */ isSingleLineTitle()1021 public boolean isSingleLineTitle() { 1022 return mSingleLineTitle; 1023 } 1024 1025 /** 1026 * Sets whether to reserve the space of this Preference icon view when no icon is provided. If 1027 * set to true, the preference will be offset as if it would have the icon and thus aligned with 1028 * other preferences having icons. 1029 * 1030 * @param iconSpaceReserved set {@code true} if the space for the icon view should be reserved 1031 * 1032 * @attr ref R.styleable#Preference_android_iconSpaceReserved 1033 */ setIconSpaceReserved(boolean iconSpaceReserved)1034 public void setIconSpaceReserved(boolean iconSpaceReserved) { 1035 mIconSpaceReserved = iconSpaceReserved; 1036 notifyChanged(); 1037 } 1038 1039 /** 1040 * Returns whether the space of this preference icon view is reserved. 1041 * 1042 * @see #setIconSpaceReserved(boolean) 1043 * @return {@code true} if the space of this preference icon view is reserved 1044 * 1045 * @attr ref R.styleable#Preference_android_iconSpaceReserved 1046 */ isIconSpaceReserved()1047 public boolean isIconSpaceReserved() { 1048 return mIconSpaceReserved; 1049 } 1050 1051 /** 1052 * Call this method after the user changes the preference, but before the 1053 * internal state is set. This allows the client to ignore the user value. 1054 * 1055 * @param newValue The new value of this Preference. 1056 * @return True if the user value should be set as the preference 1057 * value (and persisted). 1058 */ callChangeListener(Object newValue)1059 public boolean callChangeListener(Object newValue) { 1060 return mOnChangeListener == null || mOnChangeListener.onPreferenceChange(this, newValue); 1061 } 1062 1063 /** 1064 * Sets the callback to be invoked when this Preference is changed by the 1065 * user (but before the internal state has been updated). 1066 * 1067 * @param onPreferenceChangeListener The callback to be invoked. 1068 */ setOnPreferenceChangeListener( OnPreferenceChangeListener onPreferenceChangeListener)1069 public void setOnPreferenceChangeListener( 1070 OnPreferenceChangeListener onPreferenceChangeListener) { 1071 mOnChangeListener = onPreferenceChangeListener; 1072 } 1073 1074 /** 1075 * Returns the callback to be invoked when this Preference is changed by the 1076 * user (but before the internal state has been updated). 1077 * 1078 * @return The callback to be invoked. 1079 */ getOnPreferenceChangeListener()1080 public OnPreferenceChangeListener getOnPreferenceChangeListener() { 1081 return mOnChangeListener; 1082 } 1083 1084 /** 1085 * Sets the callback to be invoked when this Preference is clicked. 1086 * 1087 * @param onPreferenceClickListener The callback to be invoked. 1088 */ setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener)1089 public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) { 1090 mOnClickListener = onPreferenceClickListener; 1091 } 1092 1093 /** 1094 * Returns the callback to be invoked when this Preference is clicked. 1095 * 1096 * @return The callback to be invoked. 1097 */ getOnPreferenceClickListener()1098 public OnPreferenceClickListener getOnPreferenceClickListener() { 1099 return mOnClickListener; 1100 } 1101 1102 /** 1103 * @hide 1104 */ 1105 @RestrictTo(LIBRARY_GROUP) performClick(View view)1106 protected void performClick(View view) { 1107 performClick(); 1108 } 1109 1110 /** 1111 * Called when a click should be performed. 1112 * 1113 * @hide 1114 */ 1115 @RestrictTo(LIBRARY_GROUP) performClick()1116 public void performClick() { 1117 1118 if (!isEnabled()) { 1119 return; 1120 } 1121 1122 onClick(); 1123 1124 if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) { 1125 return; 1126 } 1127 1128 PreferenceManager preferenceManager = getPreferenceManager(); 1129 if (preferenceManager != null) { 1130 PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager 1131 .getOnPreferenceTreeClickListener(); 1132 if (listener != null && listener.onPreferenceTreeClick(this)) { 1133 return; 1134 } 1135 } 1136 1137 if (mIntent != null) { 1138 Context context = getContext(); 1139 context.startActivity(mIntent); 1140 } 1141 } 1142 1143 /** 1144 * Returns the {@link android.content.Context} of this Preference. 1145 * Each Preference in a Preference hierarchy can be 1146 * from different Context (for example, if multiple activities provide preferences into a single 1147 * {@link PreferenceFragmentCompat}). This Context will be used to save the Preference values. 1148 * 1149 * @return The Context of this Preference. 1150 */ getContext()1151 public Context getContext() { 1152 return mContext; 1153 } 1154 1155 /** 1156 * Returns the {@link android.content.SharedPreferences} where this Preference can read its 1157 * value(s). Usually, it's easier to use one of the helper read methods: 1158 * {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)}, 1159 * {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)}, 1160 * {@link #getPersistedString(String)}. 1161 * 1162 * @return the {@link SharedPreferences} where this Preference reads its value(s). If this 1163 * preference is not attached to a Preference hierarchy or if a 1164 * {@link PreferenceDataStore} has been set, this method returns {@code null}. 1165 * @see #setPreferenceDataStore(PreferenceDataStore) 1166 */ getSharedPreferences()1167 public SharedPreferences getSharedPreferences() { 1168 if (mPreferenceManager == null || getPreferenceDataStore() != null) { 1169 return null; 1170 } 1171 1172 return mPreferenceManager.getSharedPreferences(); 1173 } 1174 1175 /** 1176 * Compares Preference objects based on order (if set), otherwise alphabetically on the titles. 1177 * 1178 * @param another The Preference to compare to this one. 1179 * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>; 1180 * greater than 0 if this Preference sorts after <var>another</var>. 1181 */ 1182 @Override compareTo(@onNull Preference another)1183 public int compareTo(@NonNull Preference another) { 1184 if (mOrder != another.mOrder) { 1185 // Do order comparison 1186 return mOrder - another.mOrder; 1187 } else if (mTitle == another.mTitle) { 1188 // If titles are null or share same object comparison 1189 return 0; 1190 } else if (mTitle == null) { 1191 return 1; 1192 } else if (another.mTitle == null) { 1193 return -1; 1194 } else { 1195 // Do name comparison 1196 return mTitle.toString().compareToIgnoreCase(another.mTitle.toString()); 1197 } 1198 } 1199 1200 /** 1201 * Sets the internal change listener. 1202 * 1203 * @param listener The listener. 1204 * @see #notifyChanged() 1205 */ setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener)1206 final void setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener) { 1207 mListener = listener; 1208 } 1209 1210 /** 1211 * Should be called when the data of this {@link Preference} has changed. 1212 */ notifyChanged()1213 protected void notifyChanged() { 1214 if (mListener != null) { 1215 mListener.onPreferenceChange(this); 1216 } 1217 } 1218 1219 /** 1220 * Should be called when a Preference has been 1221 * added/removed from this group, or the ordering should be 1222 * re-evaluated. 1223 */ notifyHierarchyChanged()1224 protected void notifyHierarchyChanged() { 1225 if (mListener != null) { 1226 mListener.onPreferenceHierarchyChange(this); 1227 } 1228 } 1229 1230 /** 1231 * Gets the {@link PreferenceManager} that manages this Preference object's tree. 1232 * 1233 * @return The {@link PreferenceManager}. 1234 */ getPreferenceManager()1235 public PreferenceManager getPreferenceManager() { 1236 return mPreferenceManager; 1237 } 1238 1239 /** 1240 * Called when this Preference has been attached to a Preference hierarchy. 1241 * Make sure to call the super implementation. 1242 * 1243 * @param preferenceManager The PreferenceManager of the hierarchy. 1244 */ onAttachedToHierarchy(PreferenceManager preferenceManager)1245 protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { 1246 mPreferenceManager = preferenceManager; 1247 1248 if (!mHasId) { 1249 mId = preferenceManager.getNextId(); 1250 } 1251 1252 dispatchSetInitialValue(); 1253 } 1254 1255 /** 1256 * Called from {@link PreferenceGroup} to pass in an ID for reuse 1257 * @hide 1258 */ 1259 @RestrictTo(LIBRARY_GROUP) onAttachedToHierarchy(PreferenceManager preferenceManager, long id)1260 protected void onAttachedToHierarchy(PreferenceManager preferenceManager, long id) { 1261 mId = id; 1262 mHasId = true; 1263 try { 1264 onAttachedToHierarchy(preferenceManager); 1265 } finally { 1266 mHasId = false; 1267 } 1268 } 1269 1270 /** 1271 * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set null to remove 1272 * the current parent. 1273 * 1274 * @param parentGroup Parent preference group of this Preference or null if none. 1275 */ assignParent(@ullable PreferenceGroup parentGroup)1276 void assignParent(@Nullable PreferenceGroup parentGroup) { 1277 mParentGroup = parentGroup; 1278 } 1279 1280 /** 1281 * Called when the Preference hierarchy has been attached to the 1282 * list of preferences. This can also be called when this 1283 * Preference has been attached to a group that was already attached 1284 * to the list of preferences. 1285 */ onAttached()1286 public void onAttached() { 1287 // At this point, the hierarchy that this preference is in is connected 1288 // with all other preferences. 1289 registerDependency(); 1290 } 1291 1292 /** 1293 * Called when the Preference hierarchy has been detached from the 1294 * list of preferences. This can also be called when this 1295 * Preference has been removed from a group that was attached 1296 * to the list of preferences. 1297 */ onDetached()1298 public void onDetached() { 1299 unregisterDependency(); 1300 mWasDetached = true; 1301 } 1302 1303 /** 1304 * Returns true if {@link #onDetached()} was called. Used for handling the case when a 1305 * preference was removed, modified, and re-added to a {@link PreferenceGroup} 1306 * @hide 1307 */ 1308 @RestrictTo(LIBRARY) wasDetached()1309 public final boolean wasDetached() { 1310 return mWasDetached; 1311 } 1312 1313 /** 1314 * Clears the {@link #wasDetached()} status 1315 * @hide 1316 */ 1317 @RestrictTo(LIBRARY) clearWasDetached()1318 public final void clearWasDetached() { 1319 mWasDetached = false; 1320 } 1321 registerDependency()1322 private void registerDependency() { 1323 1324 if (TextUtils.isEmpty(mDependencyKey)) return; 1325 1326 Preference preference = findPreferenceInHierarchy(mDependencyKey); 1327 if (preference != null) { 1328 preference.registerDependent(this); 1329 } else { 1330 throw new IllegalStateException("Dependency \"" + mDependencyKey 1331 + "\" not found for preference \"" + mKey + "\" (title: \"" + mTitle + "\""); 1332 } 1333 } 1334 unregisterDependency()1335 private void unregisterDependency() { 1336 if (mDependencyKey != null) { 1337 final Preference oldDependency = findPreferenceInHierarchy(mDependencyKey); 1338 if (oldDependency != null) { 1339 oldDependency.unregisterDependent(this); 1340 } 1341 } 1342 } 1343 1344 /** 1345 * Finds a Preference in this hierarchy (the whole thing, 1346 * even above/below your {@link PreferenceScreen} screen break) with the given 1347 * key. 1348 * <p> 1349 * This only functions after we have been attached to a hierarchy. 1350 * 1351 * @param key The key of the Preference to find. 1352 * @return The Preference that uses the given key. 1353 */ findPreferenceInHierarchy(String key)1354 protected Preference findPreferenceInHierarchy(String key) { 1355 if (TextUtils.isEmpty(key) || mPreferenceManager == null) { 1356 return null; 1357 } 1358 1359 return mPreferenceManager.findPreference(key); 1360 } 1361 1362 /** 1363 * Adds a dependent Preference on this Preference so we can notify it. 1364 * Usually, the dependent Preference registers itself (it's good for it to 1365 * know it depends on something), so please use 1366 * {@link Preference#setDependency(String)} on the dependent Preference. 1367 * 1368 * @param dependent The dependent Preference that will be enabled/disabled 1369 * according to the state of this Preference. 1370 */ registerDependent(Preference dependent)1371 private void registerDependent(Preference dependent) { 1372 if (mDependents == null) { 1373 mDependents = new ArrayList<Preference>(); 1374 } 1375 1376 mDependents.add(dependent); 1377 1378 dependent.onDependencyChanged(this, shouldDisableDependents()); 1379 } 1380 1381 /** 1382 * Removes a dependent Preference on this Preference. 1383 * 1384 * @param dependent The dependent Preference that will be enabled/disabled 1385 * according to the state of this Preference. 1386 */ unregisterDependent(Preference dependent)1387 private void unregisterDependent(Preference dependent) { 1388 if (mDependents != null) { 1389 mDependents.remove(dependent); 1390 } 1391 } 1392 1393 /** 1394 * Notifies any listening dependents of a change that affects the 1395 * dependency. 1396 * 1397 * @param disableDependents Whether this Preference should disable 1398 * its dependents. 1399 */ notifyDependencyChange(boolean disableDependents)1400 public void notifyDependencyChange(boolean disableDependents) { 1401 final List<Preference> dependents = mDependents; 1402 1403 if (dependents == null) { 1404 return; 1405 } 1406 1407 final int dependentsCount = dependents.size(); 1408 for (int i = 0; i < dependentsCount; i++) { 1409 dependents.get(i).onDependencyChanged(this, disableDependents); 1410 } 1411 } 1412 1413 /** 1414 * Called when the dependency changes. 1415 * 1416 * @param dependency The Preference that this Preference depends on. 1417 * @param disableDependent Set true to disable this Preference. 1418 */ onDependencyChanged(Preference dependency, boolean disableDependent)1419 public void onDependencyChanged(Preference dependency, boolean disableDependent) { 1420 if (mDependencyMet == disableDependent) { 1421 mDependencyMet = !disableDependent; 1422 1423 // Enabled state can change dependent preferences' states, so notify 1424 notifyDependencyChange(shouldDisableDependents()); 1425 1426 notifyChanged(); 1427 } 1428 } 1429 1430 /** 1431 * Called when the implicit parent dependency changes. 1432 * 1433 * @param parent The Preference that this Preference depends on. 1434 * @param disableChild Set true to disable this Preference. 1435 */ onParentChanged(Preference parent, boolean disableChild)1436 public void onParentChanged(Preference parent, boolean disableChild) { 1437 if (mParentDependencyMet == disableChild) { 1438 mParentDependencyMet = !disableChild; 1439 1440 // Enabled state can change dependent preferences' states, so notify 1441 notifyDependencyChange(shouldDisableDependents()); 1442 1443 notifyChanged(); 1444 } 1445 } 1446 1447 /** 1448 * Checks whether this preference's dependents should currently be 1449 * disabled. 1450 * 1451 * @return True if the dependents should be disabled, otherwise false. 1452 */ shouldDisableDependents()1453 public boolean shouldDisableDependents() { 1454 return !isEnabled(); 1455 } 1456 1457 /** 1458 * Sets the key of a Preference that this Preference will depend on. If that 1459 * Preference is not set or is off, this Preference will be disabled. 1460 * 1461 * @param dependencyKey The key of the Preference that this depends on. 1462 */ setDependency(String dependencyKey)1463 public void setDependency(String dependencyKey) { 1464 // Unregister the old dependency, if we had one 1465 unregisterDependency(); 1466 1467 // Register the new 1468 mDependencyKey = dependencyKey; 1469 registerDependency(); 1470 } 1471 1472 /** 1473 * Returns the key of the dependency on this Preference. 1474 * 1475 * @return The key of the dependency. 1476 * @see #setDependency(String) 1477 */ getDependency()1478 public String getDependency() { 1479 return mDependencyKey; 1480 } 1481 1482 /** 1483 * Returns the {@link PreferenceGroup} which is this Preference assigned to or null if this 1484 * preference is not assigned to any group or is a root Preference. 1485 * 1486 * @return The parent PreferenceGroup or null if not attached to any. 1487 */ 1488 @Nullable getParent()1489 public PreferenceGroup getParent() { 1490 return mParentGroup; 1491 } 1492 1493 /** 1494 * Called when this Preference is being removed from the hierarchy. You 1495 * should remove any references to this Preference that you know about. Make 1496 * sure to call through to the superclass implementation. 1497 */ onPrepareForRemoval()1498 protected void onPrepareForRemoval() { 1499 unregisterDependency(); 1500 } 1501 1502 /** 1503 * Sets the default value for this Preference, which will be set either if 1504 * persistence is off or persistence is on and the preference is not found 1505 * in the persistent storage. 1506 * 1507 * @param defaultValue The default value. 1508 */ setDefaultValue(Object defaultValue)1509 public void setDefaultValue(Object defaultValue) { 1510 mDefaultValue = defaultValue; 1511 } 1512 dispatchSetInitialValue()1513 private void dispatchSetInitialValue() { 1514 if (getPreferenceDataStore() != null) { 1515 onSetInitialValue(true, mDefaultValue); 1516 return; 1517 } 1518 1519 // By now, we know if we are persistent. 1520 final boolean shouldPersist = shouldPersist(); 1521 if (!shouldPersist || !getSharedPreferences().contains(mKey)) { 1522 if (mDefaultValue != null) { 1523 onSetInitialValue(false, mDefaultValue); 1524 } 1525 } else { 1526 onSetInitialValue(true, null); 1527 } 1528 } 1529 1530 /** 1531 * Implement this to set the initial value of the Preference. 1532 * 1533 * <p>If <var>restorePersistedValue</var> is true, you should restore the 1534 * Preference value from the {@link android.content.SharedPreferences}. If 1535 * <var>restorePersistedValue</var> is false, you should set the Preference 1536 * value to defaultValue that is given (and possibly store to SharedPreferences 1537 * if {@link #shouldPersist()} is true). 1538 * 1539 * <p>In case of using {@link PreferenceDataStore}, the <var>restorePersistedValue</var> is 1540 * always {@code true} but the default value (if provided) is set. 1541 * 1542 * <p>This may not always be called. One example is if it should not persist 1543 * but there is no default value given. 1544 * 1545 * @param restorePersistedValue True to restore the persisted value; 1546 * false to use the given <var>defaultValue</var>. 1547 * @param defaultValue The default value for this Preference. Only use this 1548 * if <var>restorePersistedValue</var> is false. 1549 */ onSetInitialValue(boolean restorePersistedValue, Object defaultValue)1550 protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { 1551 } 1552 tryCommit(@onNull SharedPreferences.Editor editor)1553 private void tryCommit(@NonNull SharedPreferences.Editor editor) { 1554 if (mPreferenceManager.shouldCommit()) { 1555 editor.apply(); 1556 } 1557 } 1558 1559 /** 1560 * Attempts to persist a {@link String} if this Preference is persistent. 1561 * 1562 * <p>The returned value doesn't reflect whether the given value was persisted, since we may not 1563 * necessarily commit if there will be a batch commit later. 1564 * 1565 * @param value The value to persist. 1566 * @return {@code true} if the Preference is persistent, {@code false} otherwise 1567 * @see #getPersistedString(String) 1568 */ persistString(String value)1569 protected boolean persistString(String value) { 1570 if (!shouldPersist()) { 1571 return false; 1572 } 1573 1574 // Shouldn't store null 1575 if (TextUtils.equals(value, getPersistedString(null))) { 1576 // It's already there, so the same as persisting 1577 return true; 1578 } 1579 1580 PreferenceDataStore dataStore = getPreferenceDataStore(); 1581 if (dataStore != null) { 1582 dataStore.putString(mKey, value); 1583 } else { 1584 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1585 editor.putString(mKey, value); 1586 tryCommit(editor); 1587 } 1588 return true; 1589 } 1590 1591 /** 1592 * Attempts to get a persisted set of Strings if this Preference is persistent. 1593 * 1594 * @param defaultReturnValue The default value to return if either the 1595 * Preference is not persistent or the Preference is not in the 1596 * shared preferences. 1597 * @return the value from the storage or the default return value 1598 * @see #persistString(String) 1599 */ getPersistedString(String defaultReturnValue)1600 protected String getPersistedString(String defaultReturnValue) { 1601 if (!shouldPersist()) { 1602 return defaultReturnValue; 1603 } 1604 1605 PreferenceDataStore dataStore = getPreferenceDataStore(); 1606 if (dataStore != null) { 1607 return dataStore.getString(mKey, defaultReturnValue); 1608 } 1609 1610 return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue); 1611 } 1612 1613 /** 1614 * Attempts to persist a set of Strings if this Preference is persistent. 1615 * 1616 * <p>The returned value doesn't reflect whether the given value was persisted, since we may not 1617 * necessarily commit if there will be a batch commit later. 1618 * 1619 * @param values the values to persist 1620 * @return {@code true} if the Preference is persistent, {@code false} otherwise 1621 * @see #getPersistedStringSet(Set) 1622 */ persistStringSet(Set<String> values)1623 public boolean persistStringSet(Set<String> values) { 1624 if (!shouldPersist()) { 1625 return false; 1626 } 1627 1628 // Shouldn't store null 1629 if (values.equals(getPersistedStringSet(null))) { 1630 // It's already there, so the same as persisting 1631 return true; 1632 } 1633 1634 PreferenceDataStore dataStore = getPreferenceDataStore(); 1635 if (dataStore != null) { 1636 dataStore.putStringSet(mKey, values); 1637 } else { 1638 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1639 editor.putStringSet(mKey, values); 1640 tryCommit(editor); 1641 } 1642 return true; 1643 } 1644 1645 /** 1646 * Attempts to get a persisted set of Strings if this Preference is persistent. 1647 * 1648 * @param defaultReturnValue the default value to return if either this Preference is not 1649 * persistent or this Preference is not present 1650 * @return the value from the storage or the default return value 1651 * @see #persistStringSet(Set) 1652 */ getPersistedStringSet(Set<String> defaultReturnValue)1653 public Set<String> getPersistedStringSet(Set<String> defaultReturnValue) { 1654 if (!shouldPersist()) { 1655 return defaultReturnValue; 1656 } 1657 1658 PreferenceDataStore dataStore = getPreferenceDataStore(); 1659 if (dataStore != null) { 1660 return dataStore.getStringSet(mKey, defaultReturnValue); 1661 } 1662 1663 return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue); 1664 } 1665 1666 /** 1667 * Attempts to persist an {@link Integer} if this Preference is persistent. 1668 * 1669 * <p>The returned value doesn't reflect whether the given value was persisted, since we may not 1670 * necessarily commit if there will be a batch commit later. 1671 * 1672 * @param value The value to persist. 1673 * @return {@code true} if the Preference is persistent, {@code false} otherwise 1674 * @see #persistString(String) 1675 * @see #getPersistedInt(int) 1676 */ persistInt(int value)1677 protected boolean persistInt(int value) { 1678 if (!shouldPersist()) { 1679 return false; 1680 } 1681 1682 if (value == getPersistedInt(~value)) { 1683 // It's already there, so the same as persisting 1684 return true; 1685 } 1686 1687 PreferenceDataStore dataStore = getPreferenceDataStore(); 1688 if (dataStore != null) { 1689 dataStore.putInt(mKey, value); 1690 } else { 1691 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1692 editor.putInt(mKey, value); 1693 tryCommit(editor); 1694 } 1695 return true; 1696 } 1697 1698 /** 1699 * Attempts to get a persisted {@link Integer} if this Preference is persistent. 1700 * 1701 * @param defaultReturnValue The default value to return if either this 1702 * Preference is not persistent or this Preference is not in the 1703 * SharedPreferences. 1704 * @return the value from the storage or the default return value 1705 * @see #getPersistedString(String) 1706 * @see #persistInt(int) 1707 */ getPersistedInt(int defaultReturnValue)1708 protected int getPersistedInt(int defaultReturnValue) { 1709 if (!shouldPersist()) { 1710 return defaultReturnValue; 1711 } 1712 1713 PreferenceDataStore dataStore = getPreferenceDataStore(); 1714 if (dataStore != null) { 1715 return dataStore.getInt(mKey, defaultReturnValue); 1716 } 1717 1718 return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue); 1719 } 1720 1721 /** 1722 * Attempts to persist a {@link Float} if this Preference is persistent. 1723 * 1724 * <p>The returned value doesn't reflect whether the given value was persisted, since we may not 1725 * necessarily commit if there will be a batch commit later. 1726 * 1727 * @param value The value to persist. 1728 * @return {@code true} if the Preference is persistent, {@code false} otherwise 1729 * @see #persistString(String) 1730 * @see #getPersistedFloat(float) 1731 */ persistFloat(float value)1732 protected boolean persistFloat(float value) { 1733 if (!shouldPersist()) { 1734 return false; 1735 } 1736 1737 if (value == getPersistedFloat(Float.NaN)) { 1738 // It's already there, so the same as persisting 1739 return true; 1740 } 1741 1742 PreferenceDataStore dataStore = getPreferenceDataStore(); 1743 if (dataStore != null) { 1744 dataStore.putFloat(mKey, value); 1745 } else { 1746 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1747 editor.putFloat(mKey, value); 1748 tryCommit(editor); 1749 } 1750 return true; 1751 } 1752 1753 /** 1754 * Attempts to get a persisted {@link Float} if this Preference is persistent. 1755 * 1756 * @param defaultReturnValue The default value to return if either this 1757 * Preference is not persistent or this Preference is not in the 1758 * SharedPreferences. 1759 * @return the value from the storage or the default return value 1760 * @see #getPersistedString(String) 1761 * @see #persistFloat(float) 1762 */ getPersistedFloat(float defaultReturnValue)1763 protected float getPersistedFloat(float defaultReturnValue) { 1764 if (!shouldPersist()) { 1765 return defaultReturnValue; 1766 } 1767 1768 PreferenceDataStore dataStore = getPreferenceDataStore(); 1769 if (dataStore != null) { 1770 return dataStore.getFloat(mKey, defaultReturnValue); 1771 } 1772 1773 return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue); 1774 } 1775 1776 /** 1777 * Attempts to persist a {@link Long} if this Preference is persistent. 1778 * 1779 * <p>The returned value doesn't reflect whether the given value was persisted, since we may not 1780 * necessarily commit if there will be a batch commit later. 1781 * 1782 * @param value The value to persist. 1783 * @return {@code true} if the Preference is persistent, {@code false} otherwise 1784 * @see #persistString(String) 1785 * @see #getPersistedLong(long) 1786 */ persistLong(long value)1787 protected boolean persistLong(long value) { 1788 if (!shouldPersist()) { 1789 return false; 1790 } 1791 1792 if (value == getPersistedLong(~value)) { 1793 // It's already there, so the same as persisting 1794 return true; 1795 } 1796 1797 PreferenceDataStore dataStore = getPreferenceDataStore(); 1798 if (dataStore != null) { 1799 dataStore.putLong(mKey, value); 1800 } else { 1801 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1802 editor.putLong(mKey, value); 1803 tryCommit(editor); 1804 } 1805 return true; 1806 } 1807 1808 /** 1809 * Attempts to get a persisted {@link Long} if this Preference is persistent. 1810 * 1811 * @param defaultReturnValue The default value to return if either this 1812 * Preference is not persistent or this Preference is not in the 1813 * SharedPreferences. 1814 * @return the value from the storage or the default return value 1815 * @see #getPersistedString(String) 1816 * @see #persistLong(long) 1817 */ getPersistedLong(long defaultReturnValue)1818 protected long getPersistedLong(long defaultReturnValue) { 1819 if (!shouldPersist()) { 1820 return defaultReturnValue; 1821 } 1822 1823 PreferenceDataStore dataStore = getPreferenceDataStore(); 1824 if (dataStore != null) { 1825 return dataStore.getLong(mKey, defaultReturnValue); 1826 } 1827 1828 return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue); 1829 } 1830 1831 /** 1832 * Attempts to persist a {@link Boolean} if this Preference is persistent. 1833 * 1834 * <p>The returned value doesn't reflect whether the given value was persisted, since we may not 1835 * necessarily commit if there will be a batch commit later. 1836 * 1837 * @param value The value to persist. 1838 * @return {@code true} if the Preference is persistent, {@code false} otherwise 1839 * @see #persistString(String) 1840 * @see #getPersistedBoolean(boolean) 1841 */ persistBoolean(boolean value)1842 protected boolean persistBoolean(boolean value) { 1843 if (!shouldPersist()) { 1844 return false; 1845 } 1846 1847 if (value == getPersistedBoolean(!value)) { 1848 // It's already there, so the same as persisting 1849 return true; 1850 } 1851 1852 PreferenceDataStore dataStore = getPreferenceDataStore(); 1853 if (dataStore != null) { 1854 dataStore.putBoolean(mKey, value); 1855 } else { 1856 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1857 editor.putBoolean(mKey, value); 1858 tryCommit(editor); 1859 } 1860 return true; 1861 } 1862 1863 /** 1864 * Attempts to get a persisted {@link Boolean} if this Preference is persistent. 1865 * 1866 * @param defaultReturnValue The default value to return if either this 1867 * Preference is not persistent or this Preference is not in the 1868 * SharedPreferences. 1869 * @return the value from the storage or the default return value 1870 * @see #getPersistedString(String) 1871 * @see #persistBoolean(boolean) 1872 */ getPersistedBoolean(boolean defaultReturnValue)1873 protected boolean getPersistedBoolean(boolean defaultReturnValue) { 1874 if (!shouldPersist()) { 1875 return defaultReturnValue; 1876 } 1877 1878 PreferenceDataStore dataStore = getPreferenceDataStore(); 1879 if (dataStore != null) { 1880 return dataStore.getBoolean(mKey, defaultReturnValue); 1881 } 1882 1883 return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue); 1884 } 1885 1886 @Override toString()1887 public String toString() { 1888 return getFilterableStringBuilder().toString(); 1889 } 1890 1891 /** 1892 * Returns the text that will be used to filter this Preference depending on 1893 * user input. 1894 * <p> 1895 * If overriding and calling through to the superclass, make sure to prepend 1896 * your additions with a space. 1897 * 1898 * @return Text as a {@link StringBuilder} that will be used to filter this 1899 * preference. By default, this is the title and summary 1900 * (concatenated with a space). 1901 */ getFilterableStringBuilder()1902 StringBuilder getFilterableStringBuilder() { 1903 StringBuilder sb = new StringBuilder(); 1904 CharSequence title = getTitle(); 1905 if (!TextUtils.isEmpty(title)) { 1906 sb.append(title).append(' '); 1907 } 1908 CharSequence summary = getSummary(); 1909 if (!TextUtils.isEmpty(summary)) { 1910 sb.append(summary).append(' '); 1911 } 1912 if (sb.length() > 0) { 1913 // Drop the last space 1914 sb.setLength(sb.length() - 1); 1915 } 1916 return sb; 1917 } 1918 1919 /** 1920 * Store this Preference hierarchy's frozen state into the given container. 1921 * 1922 * @param container The Bundle in which to save the instance of this Preference. 1923 * 1924 * @see #restoreHierarchyState 1925 * @see #onSaveInstanceState 1926 */ saveHierarchyState(Bundle container)1927 public void saveHierarchyState(Bundle container) { 1928 dispatchSaveInstanceState(container); 1929 } 1930 1931 /** 1932 * Called by {@link #saveHierarchyState} to store the instance for this Preference and its 1933 * children. May be overridden to modify how the save happens for children. For example, some 1934 * Preference objects may want to not store an instance for their children. 1935 * 1936 * @param container The Bundle in which to save the instance of this Preference. 1937 * 1938 * @see #saveHierarchyState 1939 * @see #onSaveInstanceState 1940 */ dispatchSaveInstanceState(Bundle container)1941 void dispatchSaveInstanceState(Bundle container) { 1942 if (hasKey()) { 1943 mBaseMethodCalled = false; 1944 Parcelable state = onSaveInstanceState(); 1945 if (!mBaseMethodCalled) { 1946 throw new IllegalStateException( 1947 "Derived class did not call super.onSaveInstanceState()"); 1948 } 1949 if (state != null) { 1950 container.putParcelable(mKey, state); 1951 } 1952 } 1953 } 1954 1955 /** 1956 * Hook allowing a Preference to generate a representation of its internal 1957 * state that can later be used to create a new instance with that same 1958 * state. This state should only contain information that is not persistent 1959 * or can be reconstructed later. 1960 * 1961 * @return A Parcelable object containing the current dynamic state of 1962 * this Preference, or null if there is nothing interesting to save. 1963 * The default implementation returns null. 1964 * @see #onRestoreInstanceState 1965 * @see #saveHierarchyState 1966 */ onSaveInstanceState()1967 protected Parcelable onSaveInstanceState() { 1968 mBaseMethodCalled = true; 1969 return BaseSavedState.EMPTY_STATE; 1970 } 1971 1972 /** 1973 * Restore this Preference hierarchy's previously saved state from the given container. 1974 * 1975 * @param container The Bundle that holds the previously saved state. 1976 * 1977 * @see #saveHierarchyState 1978 * @see #onRestoreInstanceState 1979 */ restoreHierarchyState(Bundle container)1980 public void restoreHierarchyState(Bundle container) { 1981 dispatchRestoreInstanceState(container); 1982 } 1983 1984 /** 1985 * Called by {@link #restoreHierarchyState} to retrieve the saved state for this 1986 * Preference and its children. May be overridden to modify how restoring 1987 * happens to the children of a Preference. For example, some Preference objects may 1988 * not want to save state for their children. 1989 * 1990 * @param container The Bundle that holds the previously saved state. 1991 * @see #restoreHierarchyState 1992 * @see #onRestoreInstanceState 1993 */ dispatchRestoreInstanceState(Bundle container)1994 void dispatchRestoreInstanceState(Bundle container) { 1995 if (hasKey()) { 1996 Parcelable state = container.getParcelable(mKey); 1997 if (state != null) { 1998 mBaseMethodCalled = false; 1999 onRestoreInstanceState(state); 2000 if (!mBaseMethodCalled) { 2001 throw new IllegalStateException( 2002 "Derived class did not call super.onRestoreInstanceState()"); 2003 } 2004 } 2005 } 2006 } 2007 2008 /** 2009 * Hook allowing a Preference to re-apply a representation of its internal 2010 * state that had previously been generated by {@link #onSaveInstanceState}. 2011 * This function will never be called with a null state. 2012 * 2013 * @param state The saved state that had previously been returned by 2014 * {@link #onSaveInstanceState}. 2015 * @see #onSaveInstanceState 2016 * @see #restoreHierarchyState 2017 */ onRestoreInstanceState(Parcelable state)2018 protected void onRestoreInstanceState(Parcelable state) { 2019 mBaseMethodCalled = true; 2020 if (state != BaseSavedState.EMPTY_STATE && state != null) { 2021 throw new IllegalArgumentException("Wrong state class -- expecting Preference State"); 2022 } 2023 } 2024 2025 /** 2026 * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information 2027 * about the View for this Preference. 2028 */ 2029 @CallSuper onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info)2030 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) { 2031 } 2032 2033 /** 2034 * A base class for managing the instance state of a {@link Preference}. 2035 */ 2036 public static class BaseSavedState extends AbsSavedState { BaseSavedState(Parcel source)2037 public BaseSavedState(Parcel source) { 2038 super(source); 2039 } 2040 BaseSavedState(Parcelable superState)2041 public BaseSavedState(Parcelable superState) { 2042 super(superState); 2043 } 2044 2045 public static final Parcelable.Creator<BaseSavedState> CREATOR = 2046 new Parcelable.Creator<BaseSavedState>() { 2047 @Override 2048 public BaseSavedState createFromParcel(Parcel in) { 2049 return new BaseSavedState(in); 2050 } 2051 2052 @Override 2053 public BaseSavedState[] newArray(int size) { 2054 return new BaseSavedState[size]; 2055 } 2056 }; 2057 } 2058 2059 } 2060