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