1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.preference; 18 19 import android.annotation.CallSuper; 20 import android.annotation.DrawableRes; 21 import android.annotation.LayoutRes; 22 import android.annotation.Nullable; 23 import android.annotation.StringRes; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.SharedPreferences; 27 import android.content.res.TypedArray; 28 import android.graphics.drawable.Drawable; 29 import android.os.Bundle; 30 import android.os.Parcel; 31 import android.os.Parcelable; 32 import android.text.TextUtils; 33 import android.util.AttributeSet; 34 import android.view.AbsSavedState; 35 import android.view.KeyEvent; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.ImageView; 40 import android.widget.ListView; 41 import android.widget.TextView; 42 43 import com.android.internal.util.CharSequences; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import 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 */ 88 public 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 */ onPreferenceChange(Preference preference, Object newValue)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 */ onPreferenceClick(Preference preference)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 */ onPreferenceChange(Preference preference)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 */ onPreferenceHierarchyChange(Preference preference)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 */ Preference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 */ Preference(Context context, AttributeSet attrs, int defStyleAttr)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 */ Preference(Context context, AttributeSet attrs)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 */ Preference(Context context)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 */ onGetDefaultValue(TypedArray a, int index)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 */ setIntent(Intent intent)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 */ getIntent()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 */ setFragment(String fragment)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 */ getFragment()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 */ setPreferenceDataStore(PreferenceDataStore dataStore)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 getPreferenceDataStore()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 */ getExtras()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 */ peekExtras()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 */ setLayoutResource(@ayoutRes int layoutResId)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 getLayoutResource()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 */ setWidgetLayoutResource(@ayoutRes int widgetLayoutResId)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 getWidgetLayoutResource()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 */ getView(View convertView, ViewGroup parent)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 onCreateView(ViewGroup parent)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 onBindView(View view)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 */ setEnabledStateOnViews(View v, boolean enabled)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 */ setOrder(int order)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 */ getOrder()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 */ setTitle(CharSequence title)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 */ setTitle(@tringRes int titleResId)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 getTitleRes()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 */ getTitle()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 */ setIcon(Drawable icon)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 */ setIcon(@rawableRes int iconResId)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 */ getIcon()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 */ getSummary()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 */ setSummary(CharSequence summary)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 */ setSummary(@tringRes int summaryResId)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 */ setEnabled(boolean enabled)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 */ isEnabled()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 */ setSelectable(boolean selectable)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 */ isSelectable()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 */ setShouldDisableView(boolean shouldDisableView)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 */ getShouldDisableView()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 setRecycleEnabled(boolean enabled)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 */ isRecycleEnabled()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 */ setSingleLineTitle(boolean singleLineTitle)936 public void setSingleLineTitle(boolean singleLineTitle) { 937 mHasSingleLineTitleAttr = true; 938 mSingleLineTitle = singleLineTitle; 939 notifyChanged(); 940 } 941 942 /** 943 * Gets whether the title of this preference is constrained to a single line. 944 * 945 * @see #setSingleLineTitle(boolean) 946 * @return {@code true} if the title of this preference is constrained to a single line 947 */ isSingleLineTitle()948 public boolean isSingleLineTitle() { 949 return mSingleLineTitle; 950 } 951 952 /** 953 * Sets whether to reserve the space of this Preference icon view when no icon is provided. 954 * 955 * @param iconSpaceReserved set {@code true} if the space for the icon view should be reserved 956 */ setIconSpaceReserved(boolean iconSpaceReserved)957 public void setIconSpaceReserved(boolean iconSpaceReserved) { 958 mIconSpaceReserved = iconSpaceReserved; 959 notifyChanged(); 960 } 961 962 /** 963 * Gets whether the space this preference icon view is reserved. 964 * 965 * @see #setIconSpaceReserved(boolean) 966 * @return {@code true} if the space of this preference icon view is reserved 967 */ isIconSpaceReserved()968 public boolean isIconSpaceReserved() { 969 return mIconSpaceReserved; 970 } 971 /** 972 * Returns a unique ID for this Preference. This ID should be unique across all 973 * Preference objects in a hierarchy. 974 * 975 * @return A unique ID for this Preference. 976 */ getId()977 long getId() { 978 return mId; 979 } 980 981 /** 982 * Processes a click on the preference. This includes saving the value to 983 * the {@link SharedPreferences}. However, the overridden method should 984 * call {@link #callChangeListener(Object)} to make sure the client wants to 985 * update the preference's state with the new value. 986 */ onClick()987 protected void onClick() { 988 } 989 990 /** 991 * Sets the key for this Preference, which is used as a key to the {@link SharedPreferences} or 992 * {@link PreferenceDataStore}. This should be unique for the package. 993 * 994 * @param key The key for the preference. 995 */ setKey(String key)996 public void setKey(String key) { 997 mKey = key; 998 999 if (mRequiresKey && !hasKey()) { 1000 requireKey(); 1001 } 1002 } 1003 1004 /** 1005 * Gets the key for this Preference, which is also the key used for storing values into 1006 * {@link SharedPreferences} or {@link PreferenceDataStore}. 1007 * 1008 * @return The key. 1009 */ getKey()1010 public String getKey() { 1011 return mKey; 1012 } 1013 1014 /** 1015 * Checks whether the key is present, and if it isn't throws an 1016 * exception. This should be called by subclasses that persist their preferences. 1017 * 1018 * @throws IllegalStateException If there is no key assigned. 1019 */ requireKey()1020 void requireKey() { 1021 if (mKey == null) { 1022 throw new IllegalStateException("Preference does not have a key assigned."); 1023 } 1024 1025 mRequiresKey = true; 1026 } 1027 1028 /** 1029 * Checks whether this Preference has a valid key. 1030 * 1031 * @return True if the key exists and is not a blank string, false otherwise. 1032 */ hasKey()1033 public boolean hasKey() { 1034 return !TextUtils.isEmpty(mKey); 1035 } 1036 1037 /** 1038 * Checks whether this Preference is persistent. If it is, it stores its value(s) into 1039 * the persistent {@link SharedPreferences} storage by default or into 1040 * {@link PreferenceDataStore} if assigned. 1041 * 1042 * @return True if it is persistent. 1043 */ isPersistent()1044 public boolean isPersistent() { 1045 return mPersistent; 1046 } 1047 1048 /** 1049 * Checks whether, at the given time this method is called, this Preference should store/restore 1050 * its value(s) into the {@link SharedPreferences} or into {@link PreferenceDataStore} if 1051 * assigned. This, at minimum, checks whether this Preference is persistent and it currently has 1052 * a key. Before you save/restore from the storage, check this first. 1053 * 1054 * @return True if it should persist the value. 1055 */ shouldPersist()1056 protected boolean shouldPersist() { 1057 return mPreferenceManager != null && isPersistent() && hasKey(); 1058 } 1059 1060 /** 1061 * Sets whether this Preference is persistent. When persistent, it stores its value(s) into 1062 * the persistent {@link SharedPreferences} storage by default or into 1063 * {@link PreferenceDataStore} if assigned. 1064 * 1065 * @param persistent set {@code true} if it should store its value(s) into the storage. 1066 */ setPersistent(boolean persistent)1067 public void setPersistent(boolean persistent) { 1068 mPersistent = persistent; 1069 } 1070 1071 /** 1072 * Call this method after the user changes the preference, but before the 1073 * internal state is set. This allows the client to ignore the user value. 1074 * 1075 * @param newValue The new value of this Preference. 1076 * @return True if the user value should be set as the preference 1077 * value (and persisted). 1078 */ callChangeListener(Object newValue)1079 protected boolean callChangeListener(Object newValue) { 1080 return mOnChangeListener == null || mOnChangeListener.onPreferenceChange(this, newValue); 1081 } 1082 1083 /** 1084 * Sets the callback to be invoked when this Preference is changed by the 1085 * user (but before the internal state has been updated). 1086 * 1087 * @param onPreferenceChangeListener The callback to be invoked. 1088 */ setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener)1089 public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) { 1090 mOnChangeListener = onPreferenceChangeListener; 1091 } 1092 1093 /** 1094 * Returns the callback to be invoked when this Preference is changed by the 1095 * user (but before the internal state has been updated). 1096 * 1097 * @return The callback to be invoked. 1098 */ getOnPreferenceChangeListener()1099 public OnPreferenceChangeListener getOnPreferenceChangeListener() { 1100 return mOnChangeListener; 1101 } 1102 1103 /** 1104 * Sets the callback to be invoked when this Preference is clicked. 1105 * 1106 * @param onPreferenceClickListener The callback to be invoked. 1107 */ setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener)1108 public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) { 1109 mOnClickListener = onPreferenceClickListener; 1110 } 1111 1112 /** 1113 * Returns the callback to be invoked when this Preference is clicked. 1114 * 1115 * @return The callback to be invoked. 1116 */ getOnPreferenceClickListener()1117 public OnPreferenceClickListener getOnPreferenceClickListener() { 1118 return mOnClickListener; 1119 } 1120 1121 /** 1122 * Called when a click should be performed. 1123 * 1124 * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click 1125 * listener should be called in the proper order (between other 1126 * processing). May be {@code null}. 1127 * @hide 1128 */ performClick(PreferenceScreen preferenceScreen)1129 public void performClick(PreferenceScreen preferenceScreen) { 1130 1131 if (!isEnabled()) { 1132 return; 1133 } 1134 1135 onClick(); 1136 1137 if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) { 1138 return; 1139 } 1140 1141 PreferenceManager preferenceManager = getPreferenceManager(); 1142 if (preferenceManager != null) { 1143 PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager 1144 .getOnPreferenceTreeClickListener(); 1145 if (preferenceScreen != null && listener != null 1146 && listener.onPreferenceTreeClick(preferenceScreen, this)) { 1147 return; 1148 } 1149 } 1150 1151 if (mIntent != null) { 1152 Context context = getContext(); 1153 context.startActivity(mIntent); 1154 } 1155 } 1156 1157 /** 1158 * Allows a Preference to intercept key events without having focus. 1159 * For example, SeekBarPreference uses this to intercept +/- to adjust 1160 * the progress. 1161 * @return True if the Preference handled the key. Returns false by default. 1162 * @hide 1163 */ onKey(View v, int keyCode, KeyEvent event)1164 public boolean onKey(View v, int keyCode, KeyEvent event) { 1165 return false; 1166 } 1167 1168 /** 1169 * Returns the {@link android.content.Context} of this Preference. 1170 * Each Preference in a Preference hierarchy can be 1171 * from different Context (for example, if multiple activities provide preferences into a single 1172 * {@link PreferenceActivity}). This Context will be used to save the Preference values. 1173 * 1174 * @return The Context of this Preference. 1175 */ getContext()1176 public Context getContext() { 1177 return mContext; 1178 } 1179 1180 /** 1181 * Returns the {@link SharedPreferences} where this Preference can read its 1182 * value(s). Usually, it's easier to use one of the helper read methods: 1183 * {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)}, 1184 * {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)}, 1185 * {@link #getPersistedString(String)}. To save values, see 1186 * {@link #getEditor()}. 1187 * <p> 1188 * In some cases, writes to the {@link #getEditor()} will not be committed 1189 * right away and hence not show up in the returned 1190 * {@link SharedPreferences}, this is intended behavior to improve 1191 * performance. 1192 * 1193 * @return the {@link SharedPreferences} where this Preference reads its value(s). If 1194 * this preference isn't attached to a Preference hierarchy or if 1195 * a {@link PreferenceDataStore} has been set, this method returns {@code null}. 1196 * @see #getEditor() 1197 * @see #setPreferenceDataStore(PreferenceDataStore) 1198 */ getSharedPreferences()1199 public SharedPreferences getSharedPreferences() { 1200 if (mPreferenceManager == null || getPreferenceDataStore() != null) { 1201 return null; 1202 } 1203 1204 return mPreferenceManager.getSharedPreferences(); 1205 } 1206 1207 /** 1208 * Returns an {@link SharedPreferences.Editor} where this Preference can 1209 * save its value(s). Usually it's easier to use one of the helper save 1210 * methods: {@link #persistBoolean(boolean)}, {@link #persistFloat(float)}, 1211 * {@link #persistInt(int)}, {@link #persistLong(long)}, 1212 * {@link #persistString(String)}. To read values, see 1213 * {@link #getSharedPreferences()}. If {@link #shouldCommit()} returns 1214 * true, it is this Preference's responsibility to commit. 1215 * <p> 1216 * In some cases, writes to this will not be committed right away and hence 1217 * not show up in the SharedPreferences, this is intended behavior to 1218 * improve performance. 1219 * 1220 * @return a {@link SharedPreferences.Editor} where this preference saves its value(s). If 1221 * this preference isn't attached to a Preference hierarchy or if 1222 * a {@link PreferenceDataStore} has been set, this method returns {@code null}. 1223 * @see #shouldCommit() 1224 * @see #getSharedPreferences() 1225 * @see #setPreferenceDataStore(PreferenceDataStore) 1226 */ getEditor()1227 public SharedPreferences.Editor getEditor() { 1228 if (mPreferenceManager == null || getPreferenceDataStore() != null) { 1229 return null; 1230 } 1231 1232 return mPreferenceManager.getEditor(); 1233 } 1234 1235 /** 1236 * Returns whether the {@link Preference} should commit its saved value(s) in 1237 * {@link #getEditor()}. This may return false in situations where batch 1238 * committing is being done (by the manager) to improve performance. 1239 * 1240 * <p>If this preference is using {@link PreferenceDataStore} this value is irrelevant. 1241 * 1242 * @return Whether the Preference should commit its saved value(s). 1243 * @see #getEditor() 1244 */ shouldCommit()1245 public boolean shouldCommit() { 1246 if (mPreferenceManager == null) { 1247 return false; 1248 } 1249 1250 return mPreferenceManager.shouldCommit(); 1251 } 1252 1253 /** 1254 * Compares Preference objects based on order (if set), otherwise alphabetically on the titles. 1255 * 1256 * @param another The Preference to compare to this one. 1257 * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>; 1258 * greater than 0 if this Preference sorts after <var>another</var>. 1259 */ 1260 @Override compareTo(Preference another)1261 public int compareTo(Preference another) { 1262 if (mOrder != another.mOrder) { 1263 // Do order comparison 1264 return mOrder - another.mOrder; 1265 } else if (mTitle == another.mTitle) { 1266 // If titles are null or share same object comparison 1267 return 0; 1268 } else if (mTitle == null) { 1269 return 1; 1270 } else if (another.mTitle == null) { 1271 return -1; 1272 } else { 1273 // Do name comparison 1274 return CharSequences.compareToIgnoreCase(mTitle, another.mTitle); 1275 } 1276 } 1277 1278 /** 1279 * Sets the internal change listener. 1280 * 1281 * @param listener The listener. 1282 * @see #notifyChanged() 1283 */ setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener)1284 final void setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener) { 1285 mListener = listener; 1286 } 1287 1288 /** 1289 * Should be called when the data of this {@link Preference} has changed. 1290 */ notifyChanged()1291 protected void notifyChanged() { 1292 if (mListener != null) { 1293 mListener.onPreferenceChange(this); 1294 } 1295 } 1296 1297 /** 1298 * Should be called when a Preference has been 1299 * added/removed from this group, or the ordering should be 1300 * re-evaluated. 1301 */ notifyHierarchyChanged()1302 protected void notifyHierarchyChanged() { 1303 if (mListener != null) { 1304 mListener.onPreferenceHierarchyChange(this); 1305 } 1306 } 1307 1308 /** 1309 * Gets the {@link PreferenceManager} that manages this Preference object's tree. 1310 * 1311 * @return The {@link PreferenceManager}. 1312 */ getPreferenceManager()1313 public PreferenceManager getPreferenceManager() { 1314 return mPreferenceManager; 1315 } 1316 1317 /** 1318 * Called when this Preference has been attached to a Preference hierarchy. 1319 * Make sure to call the super implementation. 1320 * 1321 * @param preferenceManager The PreferenceManager of the hierarchy. 1322 */ onAttachedToHierarchy(PreferenceManager preferenceManager)1323 protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { 1324 mPreferenceManager = preferenceManager; 1325 1326 mId = preferenceManager.getNextId(); 1327 1328 dispatchSetInitialValue(); 1329 } 1330 1331 /** 1332 * Called when the Preference hierarchy has been attached to the 1333 * {@link PreferenceActivity}. This can also be called when this 1334 * Preference has been attached to a group that was already attached 1335 * to the {@link PreferenceActivity}. 1336 */ onAttachedToActivity()1337 protected void onAttachedToActivity() { 1338 // At this point, the hierarchy that this preference is in is connected 1339 // with all other preferences. 1340 registerDependency(); 1341 } 1342 1343 /** 1344 * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set {@code null} to 1345 * remove the current parent. 1346 * 1347 * @param parentGroup Parent preference group of this Preference or {@code null} if none. 1348 */ assignParent(@ullable PreferenceGroup parentGroup)1349 void assignParent(@Nullable PreferenceGroup parentGroup) { 1350 mParentGroup = parentGroup; 1351 } 1352 registerDependency()1353 private void registerDependency() { 1354 1355 if (TextUtils.isEmpty(mDependencyKey)) return; 1356 1357 Preference preference = findPreferenceInHierarchy(mDependencyKey); 1358 if (preference != null) { 1359 preference.registerDependent(this); 1360 } else { 1361 throw new IllegalStateException("Dependency \"" + mDependencyKey 1362 + "\" not found for preference \"" + mKey + "\" (title: \"" + mTitle + "\""); 1363 } 1364 } 1365 unregisterDependency()1366 private void unregisterDependency() { 1367 if (mDependencyKey != null) { 1368 final Preference oldDependency = findPreferenceInHierarchy(mDependencyKey); 1369 if (oldDependency != null) { 1370 oldDependency.unregisterDependent(this); 1371 } 1372 } 1373 } 1374 1375 /** 1376 * Finds a Preference in this hierarchy (the whole thing, 1377 * even above/below your {@link PreferenceScreen} screen break) with the given 1378 * key. 1379 * <p> 1380 * This only functions after we have been attached to a hierarchy. 1381 * 1382 * @param key The key of the Preference to find. 1383 * @return The Preference that uses the given key. 1384 */ findPreferenceInHierarchy(String key)1385 protected Preference findPreferenceInHierarchy(String key) { 1386 if (TextUtils.isEmpty(key) || mPreferenceManager == null) { 1387 return null; 1388 } 1389 1390 return mPreferenceManager.findPreference(key); 1391 } 1392 1393 /** 1394 * Adds a dependent Preference on this Preference so we can notify it. 1395 * Usually, the dependent Preference registers itself (it's good for it to 1396 * know it depends on something), so please use 1397 * {@link Preference#setDependency(String)} on the dependent Preference. 1398 * 1399 * @param dependent The dependent Preference that will be enabled/disabled 1400 * according to the state of this Preference. 1401 */ registerDependent(Preference dependent)1402 private void registerDependent(Preference dependent) { 1403 if (mDependents == null) { 1404 mDependents = new ArrayList<Preference>(); 1405 } 1406 1407 mDependents.add(dependent); 1408 1409 dependent.onDependencyChanged(this, shouldDisableDependents()); 1410 } 1411 1412 /** 1413 * Removes a dependent Preference on this Preference. 1414 * 1415 * @param dependent The dependent Preference that will be enabled/disabled 1416 * according to the state of this Preference. 1417 * @return Returns the same Preference object, for chaining multiple calls 1418 * into a single statement. 1419 */ unregisterDependent(Preference dependent)1420 private void unregisterDependent(Preference dependent) { 1421 if (mDependents != null) { 1422 mDependents.remove(dependent); 1423 } 1424 } 1425 1426 /** 1427 * Notifies any listening dependents of a change that affects the 1428 * dependency. 1429 * 1430 * @param disableDependents Whether this Preference should disable 1431 * its dependents. 1432 */ notifyDependencyChange(boolean disableDependents)1433 public void notifyDependencyChange(boolean disableDependents) { 1434 final List<Preference> dependents = mDependents; 1435 1436 if (dependents == null) { 1437 return; 1438 } 1439 1440 final int dependentsCount = dependents.size(); 1441 for (int i = 0; i < dependentsCount; i++) { 1442 dependents.get(i).onDependencyChanged(this, disableDependents); 1443 } 1444 } 1445 1446 /** 1447 * Called when the dependency changes. 1448 * 1449 * @param dependency The Preference that this Preference depends on. 1450 * @param disableDependent Set true to disable this Preference. 1451 */ onDependencyChanged(Preference dependency, boolean disableDependent)1452 public void onDependencyChanged(Preference dependency, boolean disableDependent) { 1453 if (mDependencyMet == disableDependent) { 1454 mDependencyMet = !disableDependent; 1455 1456 // Enabled state can change dependent preferences' states, so notify 1457 notifyDependencyChange(shouldDisableDependents()); 1458 1459 notifyChanged(); 1460 } 1461 } 1462 1463 /** 1464 * Called when the implicit parent dependency changes. 1465 * 1466 * @param parent The Preference that this Preference depends on. 1467 * @param disableChild Set true to disable this Preference. 1468 */ onParentChanged(Preference parent, boolean disableChild)1469 public void onParentChanged(Preference parent, boolean disableChild) { 1470 if (mParentDependencyMet == disableChild) { 1471 mParentDependencyMet = !disableChild; 1472 1473 // Enabled state can change dependent preferences' states, so notify 1474 notifyDependencyChange(shouldDisableDependents()); 1475 1476 notifyChanged(); 1477 } 1478 } 1479 1480 /** 1481 * Checks whether this preference's dependents should currently be 1482 * disabled. 1483 * 1484 * @return True if the dependents should be disabled, otherwise false. 1485 */ shouldDisableDependents()1486 public boolean shouldDisableDependents() { 1487 return !isEnabled(); 1488 } 1489 1490 /** 1491 * Sets the key of a Preference that this Preference will depend on. If that 1492 * Preference is not set or is off, this Preference will be disabled. 1493 * 1494 * @param dependencyKey The key of the Preference that this depends on. 1495 */ setDependency(String dependencyKey)1496 public void setDependency(String dependencyKey) { 1497 // Unregister the old dependency, if we had one 1498 unregisterDependency(); 1499 1500 // Register the new 1501 mDependencyKey = dependencyKey; 1502 registerDependency(); 1503 } 1504 1505 /** 1506 * Returns the key of the dependency on this Preference. 1507 * 1508 * @return The key of the dependency. 1509 * @see #setDependency(String) 1510 */ getDependency()1511 public String getDependency() { 1512 return mDependencyKey; 1513 } 1514 1515 /** 1516 * Returns the {@link PreferenceGroup} which is this Preference assigned to or {@code null} if 1517 * this preference is not assigned to any group or is a root Preference. 1518 * 1519 * @return the parent PreferenceGroup or {@code null} if not attached to any 1520 */ 1521 @Nullable getParent()1522 public PreferenceGroup getParent() { 1523 return mParentGroup; 1524 } 1525 1526 /** 1527 * Called when this Preference is being removed from the hierarchy. You 1528 * should remove any references to this Preference that you know about. Make 1529 * sure to call through to the superclass implementation. 1530 */ 1531 @CallSuper onPrepareForRemoval()1532 protected void onPrepareForRemoval() { 1533 unregisterDependency(); 1534 } 1535 1536 /** 1537 * Sets the default value for this Preference, which will be set either if 1538 * persistence is off or persistence is on and the preference is not found 1539 * in the persistent storage. 1540 * 1541 * @param defaultValue The default value. 1542 */ setDefaultValue(Object defaultValue)1543 public void setDefaultValue(Object defaultValue) { 1544 mDefaultValue = defaultValue; 1545 } 1546 dispatchSetInitialValue()1547 private void dispatchSetInitialValue() { 1548 if (getPreferenceDataStore() != null) { 1549 onSetInitialValue(true, mDefaultValue); 1550 return; 1551 } 1552 1553 // By now, we know if we are persistent. 1554 final boolean shouldPersist = shouldPersist(); 1555 if (!shouldPersist || !getSharedPreferences().contains(mKey)) { 1556 if (mDefaultValue != null) { 1557 onSetInitialValue(false, mDefaultValue); 1558 } 1559 } else { 1560 onSetInitialValue(true, null); 1561 } 1562 } 1563 1564 /** 1565 * Implement this to set the initial value of the Preference. 1566 * 1567 * <p>If <var>restorePersistedValue</var> is true, you should restore the 1568 * Preference value from the {@link android.content.SharedPreferences}. If 1569 * <var>restorePersistedValue</var> is false, you should set the Preference 1570 * value to defaultValue that is given (and possibly store to SharedPreferences 1571 * if {@link #shouldPersist()} is true). 1572 * 1573 * <p>In case of using {@link PreferenceDataStore}, the <var>restorePersistedValue</var> is 1574 * always {@code true}. But the default value (if provided) is set. 1575 * 1576 * <p>This may not always be called. One example is if it should not persist 1577 * but there is no default value given. 1578 * 1579 * @param restorePersistedValue True to restore the persisted value; 1580 * false to use the given <var>defaultValue</var>. 1581 * @param defaultValue The default value for this Preference. Only use this 1582 * if <var>restorePersistedValue</var> is false. 1583 */ onSetInitialValue(boolean restorePersistedValue, Object defaultValue)1584 protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { 1585 } 1586 tryCommit(SharedPreferences.Editor editor)1587 private void tryCommit(SharedPreferences.Editor editor) { 1588 if (mPreferenceManager.shouldCommit()) { 1589 try { 1590 editor.apply(); 1591 } catch (AbstractMethodError unused) { 1592 // The app injected its own pre-Gingerbread 1593 // SharedPreferences.Editor implementation without 1594 // an apply method. 1595 editor.commit(); 1596 } 1597 } 1598 } 1599 1600 /** 1601 * Attempts to persist a String if this Preference is persistent. 1602 * 1603 * @param value The value to persist. 1604 * @return True if this Preference is persistent. (This is not whether the 1605 * value was persisted, since we may not necessarily commit if there 1606 * will be a batch commit later.) 1607 * @see #getPersistedString(String) 1608 */ persistString(String value)1609 protected boolean persistString(String value) { 1610 if (!shouldPersist()) { 1611 return false; 1612 } 1613 1614 // Shouldn't store null 1615 if (TextUtils.equals(value, getPersistedString(null))) { 1616 // It's already there, so the same as persisting 1617 return true; 1618 } 1619 1620 PreferenceDataStore dataStore = getPreferenceDataStore(); 1621 if (dataStore != null) { 1622 dataStore.putString(mKey, value); 1623 } else { 1624 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1625 editor.putString(mKey, value); 1626 tryCommit(editor); 1627 } 1628 return true; 1629 } 1630 1631 /** 1632 * Attempts to get a persisted String if this Preference is persistent. 1633 * 1634 * @param defaultReturnValue The default value to return if either this 1635 * Preference is not persistent or this Preference is not present. 1636 * @return The value from the data store or the default return 1637 * value. 1638 * @see #persistString(String) 1639 */ getPersistedString(String defaultReturnValue)1640 protected String getPersistedString(String defaultReturnValue) { 1641 if (!shouldPersist()) { 1642 return defaultReturnValue; 1643 } 1644 1645 PreferenceDataStore dataStore = getPreferenceDataStore(); 1646 if (dataStore != null) { 1647 return dataStore.getString(mKey, defaultReturnValue); 1648 } 1649 1650 return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue); 1651 } 1652 1653 /** 1654 * Attempts to persist a set of Strings if this Preference is persistent. 1655 * 1656 * @param values The values to persist. 1657 * @return True if this Preference is persistent. (This is not whether the 1658 * value was persisted, since we may not necessarily commit if there 1659 * will be a batch commit later.) 1660 * @see #getPersistedStringSet(Set) 1661 */ persistStringSet(Set<String> values)1662 public boolean persistStringSet(Set<String> values) { 1663 if (!shouldPersist()) { 1664 return false; 1665 } 1666 1667 // Shouldn't store null 1668 if (values.equals(getPersistedStringSet(null))) { 1669 // It's already there, so the same as persisting 1670 return true; 1671 } 1672 1673 PreferenceDataStore dataStore = getPreferenceDataStore(); 1674 if (dataStore != null) { 1675 dataStore.putStringSet(mKey, values); 1676 } else { 1677 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1678 editor.putStringSet(mKey, values); 1679 tryCommit(editor); 1680 } 1681 return true; 1682 } 1683 1684 /** 1685 * Attempts to get a persisted set of Strings if this Preference is persistent. 1686 * 1687 * @param defaultReturnValue The default value to return if either this 1688 * Preference is not persistent or this Preference is not present. 1689 * @return The value from the data store or the default return 1690 * value. 1691 * @see #persistStringSet(Set) 1692 */ getPersistedStringSet(Set<String> defaultReturnValue)1693 public Set<String> getPersistedStringSet(Set<String> defaultReturnValue) { 1694 if (!shouldPersist()) { 1695 return defaultReturnValue; 1696 } 1697 1698 PreferenceDataStore dataStore = getPreferenceDataStore(); 1699 if (dataStore != null) { 1700 return dataStore.getStringSet(mKey, defaultReturnValue); 1701 } 1702 1703 return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue); 1704 } 1705 1706 /** 1707 * Attempts to persist an int if this Preference is persistent. 1708 * 1709 * @param value The value to persist. 1710 * @return True if this Preference is persistent. (This is not whether the 1711 * value was persisted, since we may not necessarily commit if there 1712 * will be a batch commit later.) 1713 * @see #persistString(String) 1714 * @see #getPersistedInt(int) 1715 */ persistInt(int value)1716 protected boolean persistInt(int value) { 1717 if (!shouldPersist()) { 1718 return false; 1719 } 1720 1721 if (value == getPersistedInt(~value)) { 1722 // It's already there, so the same as persisting 1723 return true; 1724 } 1725 1726 PreferenceDataStore dataStore = getPreferenceDataStore(); 1727 if (dataStore != null) { 1728 dataStore.putInt(mKey, value); 1729 } else { 1730 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1731 editor.putInt(mKey, value); 1732 tryCommit(editor); 1733 } 1734 return true; 1735 } 1736 1737 /** 1738 * Attempts to get a persisted int if this Preference is persistent. 1739 * 1740 * @param defaultReturnValue The default value to return if either this 1741 * Preference is not persistent or this Preference is not present. 1742 * @return The value from the data store or the default return 1743 * value. 1744 * @see #getPersistedString(String) 1745 * @see #persistInt(int) 1746 */ getPersistedInt(int defaultReturnValue)1747 protected int getPersistedInt(int defaultReturnValue) { 1748 if (!shouldPersist()) { 1749 return defaultReturnValue; 1750 } 1751 1752 PreferenceDataStore dataStore = getPreferenceDataStore(); 1753 if (dataStore != null) { 1754 return dataStore.getInt(mKey, defaultReturnValue); 1755 } 1756 1757 return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue); 1758 } 1759 1760 /** 1761 * Attempts to persist a long if this Preference is persistent. 1762 * 1763 * @param value The value to persist. 1764 * @return True if this Preference is persistent. (This is not whether the 1765 * value was persisted, since we may not necessarily commit if there 1766 * will be a batch commit later.) 1767 * @see #persistString(String) 1768 * @see #getPersistedFloat(float) 1769 */ persistFloat(float value)1770 protected boolean persistFloat(float value) { 1771 if (!shouldPersist()) { 1772 return false; 1773 } 1774 1775 if (value == getPersistedFloat(Float.NaN)) { 1776 // It's already there, so the same as persisting 1777 return true; 1778 } 1779 1780 PreferenceDataStore dataStore = getPreferenceDataStore(); 1781 if (dataStore != null) { 1782 dataStore.putFloat(mKey, value); 1783 } else { 1784 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1785 editor.putFloat(mKey, value); 1786 tryCommit(editor); 1787 } 1788 return true; 1789 } 1790 1791 /** 1792 * Attempts to get a persisted float if this Preference is persistent. 1793 * 1794 * @param defaultReturnValue The default value to return if either this 1795 * Preference is not persistent or this Preference is not present. 1796 * @return The value from the data store or the default return 1797 * value. 1798 * @see #getPersistedString(String) 1799 * @see #persistFloat(float) 1800 */ getPersistedFloat(float defaultReturnValue)1801 protected float getPersistedFloat(float defaultReturnValue) { 1802 if (!shouldPersist()) { 1803 return defaultReturnValue; 1804 } 1805 1806 PreferenceDataStore dataStore = getPreferenceDataStore(); 1807 if (dataStore != null) { 1808 return dataStore.getFloat(mKey, defaultReturnValue); 1809 } 1810 1811 return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue); 1812 } 1813 1814 /** 1815 * Attempts to persist a long if this Preference is persistent. 1816 * 1817 * @param value The value to persist. 1818 * @return True if this Preference is persistent. (This is not whether the 1819 * value was persisted, since we may not necessarily commit if there 1820 * will be a batch commit later.) 1821 * @see #persistString(String) 1822 * @see #getPersistedLong(long) 1823 */ persistLong(long value)1824 protected boolean persistLong(long value) { 1825 if (!shouldPersist()) { 1826 return false; 1827 } 1828 1829 if (value == getPersistedLong(~value)) { 1830 // It's already there, so the same as persisting 1831 return true; 1832 } 1833 1834 PreferenceDataStore dataStore = getPreferenceDataStore(); 1835 if (dataStore != null) { 1836 dataStore.putLong(mKey, value); 1837 } else { 1838 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1839 editor.putLong(mKey, value); 1840 tryCommit(editor); 1841 } 1842 return true; 1843 } 1844 1845 /** 1846 * Attempts to get a persisted long if this Preference is persistent. 1847 * 1848 * @param defaultReturnValue The default value to return if either this 1849 * Preference is not persistent or this Preference is not present. 1850 * @return The value from the data store or the default return 1851 * value. 1852 * @see #getPersistedString(String) 1853 * @see #persistLong(long) 1854 */ getPersistedLong(long defaultReturnValue)1855 protected long getPersistedLong(long defaultReturnValue) { 1856 if (!shouldPersist()) { 1857 return defaultReturnValue; 1858 } 1859 1860 PreferenceDataStore dataStore = getPreferenceDataStore(); 1861 if (dataStore != null) { 1862 return dataStore.getLong(mKey, defaultReturnValue); 1863 } 1864 1865 return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue); 1866 } 1867 1868 /** 1869 * Attempts to persist a boolean if this Preference is persistent. 1870 * 1871 * @param value The value to persist. 1872 * @return True if this Preference is persistent. (This is not whether the 1873 * value was persisted, since we may not necessarily commit if there 1874 * will be a batch commit later.) 1875 * @see #persistString(String) 1876 * @see #getPersistedBoolean(boolean) 1877 */ persistBoolean(boolean value)1878 protected boolean persistBoolean(boolean value) { 1879 if (!shouldPersist()) { 1880 return false; 1881 } 1882 1883 if (value == getPersistedBoolean(!value)) { 1884 // It's already there, so the same as persisting 1885 return true; 1886 } 1887 1888 PreferenceDataStore dataStore = getPreferenceDataStore(); 1889 if (dataStore != null) { 1890 dataStore.putBoolean(mKey, value); 1891 } else { 1892 SharedPreferences.Editor editor = mPreferenceManager.getEditor(); 1893 editor.putBoolean(mKey, value); 1894 tryCommit(editor); 1895 } 1896 return true; 1897 } 1898 1899 /** 1900 * Attempts to get a persisted boolean if this Preference is persistent. 1901 * 1902 * @param defaultReturnValue The default value to return if either this 1903 * Preference is not persistent or this Preference is not present. 1904 * @return The value from the data store or the default return 1905 * value. 1906 * @see #getPersistedString(String) 1907 * @see #persistBoolean(boolean) 1908 */ getPersistedBoolean(boolean defaultReturnValue)1909 protected boolean getPersistedBoolean(boolean defaultReturnValue) { 1910 if (!shouldPersist()) { 1911 return defaultReturnValue; 1912 } 1913 1914 PreferenceDataStore dataStore = getPreferenceDataStore(); 1915 if (dataStore != null) { 1916 return dataStore.getBoolean(mKey, defaultReturnValue); 1917 } 1918 1919 return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue); 1920 } 1921 1922 @Override toString()1923 public String toString() { 1924 return getFilterableStringBuilder().toString(); 1925 } 1926 1927 /** 1928 * Returns the text that will be used to filter this Preference depending on 1929 * user input. 1930 * <p> 1931 * If overridding and calling through to the superclass, make sure to prepend 1932 * your additions with a space. 1933 * 1934 * @return Text as a {@link StringBuilder} that will be used to filter this 1935 * preference. By default, this is the title and summary 1936 * (concatenated with a space). 1937 */ getFilterableStringBuilder()1938 StringBuilder getFilterableStringBuilder() { 1939 StringBuilder sb = new StringBuilder(); 1940 CharSequence title = getTitle(); 1941 if (!TextUtils.isEmpty(title)) { 1942 sb.append(title).append(' '); 1943 } 1944 CharSequence summary = getSummary(); 1945 if (!TextUtils.isEmpty(summary)) { 1946 sb.append(summary).append(' '); 1947 } 1948 if (sb.length() > 0) { 1949 // Drop the last space 1950 sb.setLength(sb.length() - 1); 1951 } 1952 return sb; 1953 } 1954 1955 /** 1956 * Store this Preference hierarchy's frozen state into the given container. 1957 * 1958 * @param container The Bundle in which to save the instance of this Preference. 1959 * 1960 * @see #restoreHierarchyState 1961 * @see #onSaveInstanceState 1962 */ saveHierarchyState(Bundle container)1963 public void saveHierarchyState(Bundle container) { 1964 dispatchSaveInstanceState(container); 1965 } 1966 1967 /** 1968 * Called by {@link #saveHierarchyState} to store the instance for this Preference and its 1969 * children. May be overridden to modify how the save happens for children. For example, some 1970 * Preference objects may want to not store an instance for their children. 1971 * 1972 * @param container The Bundle in which to save the instance of this Preference. 1973 * 1974 * @see #saveHierarchyState 1975 * @see #onSaveInstanceState 1976 */ dispatchSaveInstanceState(Bundle container)1977 void dispatchSaveInstanceState(Bundle container) { 1978 if (hasKey()) { 1979 mBaseMethodCalled = false; 1980 Parcelable state = onSaveInstanceState(); 1981 if (!mBaseMethodCalled) { 1982 throw new IllegalStateException( 1983 "Derived class did not call super.onSaveInstanceState()"); 1984 } 1985 if (state != null) { 1986 container.putParcelable(mKey, state); 1987 } 1988 } 1989 } 1990 1991 /** 1992 * Hook allowing a Preference to generate a representation of its internal 1993 * state that can later be used to create a new instance with that same 1994 * state. This state should only contain information that is not persistent 1995 * or can be reconstructed later. 1996 * 1997 * @return A Parcelable object containing the current dynamic state of this Preference, or 1998 * {@code null} if there is nothing interesting to save. The default implementation 1999 * returns {@code null}. 2000 * @see #onRestoreInstanceState 2001 * @see #saveHierarchyState 2002 */ onSaveInstanceState()2003 protected Parcelable onSaveInstanceState() { 2004 mBaseMethodCalled = true; 2005 return BaseSavedState.EMPTY_STATE; 2006 } 2007 2008 /** 2009 * Restore this Preference hierarchy's previously saved state from the given container. 2010 * 2011 * @param container The Bundle that holds the previously saved state. 2012 * 2013 * @see #saveHierarchyState 2014 * @see #onRestoreInstanceState 2015 */ restoreHierarchyState(Bundle container)2016 public void restoreHierarchyState(Bundle container) { 2017 dispatchRestoreInstanceState(container); 2018 } 2019 2020 /** 2021 * Called by {@link #restoreHierarchyState} to retrieve the saved state for this 2022 * Preference and its children. May be overridden to modify how restoring 2023 * happens to the children of a Preference. For example, some Preference objects may 2024 * not want to save state for their children. 2025 * 2026 * @param container The Bundle that holds the previously saved state. 2027 * @see #restoreHierarchyState 2028 * @see #onRestoreInstanceState 2029 */ dispatchRestoreInstanceState(Bundle container)2030 void dispatchRestoreInstanceState(Bundle container) { 2031 if (hasKey()) { 2032 Parcelable state = container.getParcelable(mKey); 2033 if (state != null) { 2034 mBaseMethodCalled = false; 2035 onRestoreInstanceState(state); 2036 if (!mBaseMethodCalled) { 2037 throw new IllegalStateException( 2038 "Derived class did not call super.onRestoreInstanceState()"); 2039 } 2040 } 2041 } 2042 } 2043 2044 /** 2045 * Hook allowing a Preference to re-apply a representation of its internal state that had 2046 * previously been generated by {@link #onSaveInstanceState}. This function will never be called 2047 * with a {@code null} state. 2048 * 2049 * @param state The saved state that had previously been returned by 2050 * {@link #onSaveInstanceState}. 2051 * @see #onSaveInstanceState 2052 * @see #restoreHierarchyState 2053 */ onRestoreInstanceState(Parcelable state)2054 protected void onRestoreInstanceState(Parcelable state) { 2055 mBaseMethodCalled = true; 2056 if (state != BaseSavedState.EMPTY_STATE && state != null) { 2057 throw new IllegalArgumentException("Wrong state class -- expecting Preference State"); 2058 } 2059 } 2060 2061 /** 2062 * A base class for managing the instance state of a {@link Preference}. 2063 */ 2064 public static class BaseSavedState extends AbsSavedState { BaseSavedState(Parcel source)2065 public BaseSavedState(Parcel source) { 2066 super(source); 2067 } 2068 BaseSavedState(Parcelable superState)2069 public BaseSavedState(Parcelable superState) { 2070 super(superState); 2071 } 2072 2073 public static final Parcelable.Creator<BaseSavedState> CREATOR = 2074 new Parcelable.Creator<BaseSavedState>() { 2075 public BaseSavedState createFromParcel(Parcel in) { 2076 return new BaseSavedState(in); 2077 } 2078 2079 public BaseSavedState[] newArray(int size) { 2080 return new BaseSavedState[size]; 2081 } 2082 }; 2083 } 2084 2085 } 2086