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