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