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