• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         if (mIcon == null && mIconResId != 0) {
723             mIcon = getContext().getDrawable(mIconResId);
724         }
725         return mIcon;
726     }
727 
728     /**
729      * Returns the summary of this Preference.
730      *
731      * @return The summary.
732      * @see #setSummary(CharSequence)
733      */
getSummary()734     public CharSequence getSummary() {
735         return mSummary;
736     }
737 
738     /**
739      * Sets the summary for this Preference with a CharSequence.
740      *
741      * @param summary The summary for the preference.
742      */
setSummary(CharSequence summary)743     public void setSummary(CharSequence summary) {
744         if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) {
745             mSummary = summary;
746             notifyChanged();
747         }
748     }
749 
750     /**
751      * Sets the summary for this Preference with a resource ID.
752      *
753      * @see #setSummary(CharSequence)
754      * @param summaryResId The summary as a resource.
755      */
setSummary(@tringRes int summaryResId)756     public void setSummary(@StringRes int summaryResId) {
757         setSummary(mContext.getString(summaryResId));
758     }
759 
760     /**
761      * Sets whether this Preference is enabled. If disabled, it will
762      * not handle clicks.
763      *
764      * @param enabled Set true to enable it.
765      */
setEnabled(boolean enabled)766     public void setEnabled(boolean enabled) {
767         if (mEnabled != enabled) {
768             mEnabled = enabled;
769 
770             // Enabled state can change dependent preferences' states, so notify
771             notifyDependencyChange(shouldDisableDependents());
772 
773             notifyChanged();
774         }
775     }
776 
777     /**
778      * Checks whether this Preference should be enabled in the list.
779      *
780      * @return True if this Preference is enabled, false otherwise.
781      */
isEnabled()782     public boolean isEnabled() {
783         return mEnabled && mDependencyMet && mParentDependencyMet;
784     }
785 
786     /**
787      * Sets whether this Preference is selectable.
788      *
789      * @param selectable Set true to make it selectable.
790      */
setSelectable(boolean selectable)791     public void setSelectable(boolean selectable) {
792         if (mSelectable != selectable) {
793             mSelectable = selectable;
794             notifyChanged();
795         }
796     }
797 
798     /**
799      * Checks whether this Preference should be selectable in the list.
800      *
801      * @return True if it is selectable, false otherwise.
802      */
isSelectable()803     public boolean isSelectable() {
804         return mSelectable;
805     }
806 
807     /**
808      * Sets whether this Preference should disable its view when it gets
809      * disabled.
810      * <p>
811      * For example, set this and {@link #setEnabled(boolean)} to false for
812      * preferences that are only displaying information and 1) should not be
813      * clickable 2) should not have the view set to the disabled state.
814      *
815      * @param shouldDisableView Set true if this preference should disable its view
816      *            when the preference is disabled.
817      */
setShouldDisableView(boolean shouldDisableView)818     public void setShouldDisableView(boolean shouldDisableView) {
819         mShouldDisableView = shouldDisableView;
820         notifyChanged();
821     }
822 
823     /**
824      * Checks whether this Preference should disable its view when it's action is disabled.
825      * @see #setShouldDisableView(boolean)
826      * @return True if it should disable the view.
827      */
getShouldDisableView()828     public boolean getShouldDisableView() {
829         return mShouldDisableView;
830     }
831 
832     /**
833      * Returns a unique ID for this Preference.  This ID should be unique across all
834      * Preference objects in a hierarchy.
835      *
836      * @return A unique ID for this Preference.
837      */
getId()838     long getId() {
839         return mId;
840     }
841 
842     /**
843      * Processes a click on the preference. This includes saving the value to
844      * the {@link SharedPreferences}. However, the overridden method should
845      * call {@link #callChangeListener(Object)} to make sure the client wants to
846      * update the preference's state with the new value.
847      */
onClick()848     protected void onClick() {
849     }
850 
851     /**
852      * Sets the key for this Preference, which is used as a key to the
853      * {@link SharedPreferences}. This should be unique for the package.
854      *
855      * @param key The key for the preference.
856      */
setKey(String key)857     public void setKey(String key) {
858         mKey = key;
859 
860         if (mRequiresKey && !hasKey()) {
861             requireKey();
862         }
863     }
864 
865     /**
866      * Gets the key for this Preference, which is also the key used for storing
867      * values into SharedPreferences.
868      *
869      * @return The key.
870      */
getKey()871     public String getKey() {
872         return mKey;
873     }
874 
875     /**
876      * Checks whether the key is present, and if it isn't throws an
877      * exception. This should be called by subclasses that store preferences in
878      * the {@link SharedPreferences}.
879      *
880      * @throws IllegalStateException If there is no key assigned.
881      */
requireKey()882     void requireKey() {
883         if (mKey == null) {
884             throw new IllegalStateException("Preference does not have a key assigned.");
885         }
886 
887         mRequiresKey = true;
888     }
889 
890     /**
891      * Checks whether this Preference has a valid key.
892      *
893      * @return True if the key exists and is not a blank string, false otherwise.
894      */
hasKey()895     public boolean hasKey() {
896         return !TextUtils.isEmpty(mKey);
897     }
898 
899     /**
900      * Checks whether this Preference is persistent. If it is, it stores its value(s) into
901      * the persistent {@link SharedPreferences} storage.
902      *
903      * @return True if it is persistent.
904      */
isPersistent()905     public boolean isPersistent() {
906         return mPersistent;
907     }
908 
909     /**
910      * Checks whether, at the given time this method is called,
911      * this Preference should store/restore its value(s) into the
912      * {@link SharedPreferences}. This, at minimum, checks whether this
913      * Preference is persistent and it currently has a key. Before you
914      * save/restore from the {@link SharedPreferences}, check this first.
915      *
916      * @return True if it should persist the value.
917      */
shouldPersist()918     protected boolean shouldPersist() {
919         return mPreferenceManager != null && isPersistent() && hasKey();
920     }
921 
922     /**
923      * Sets whether this Preference is persistent. When persistent,
924      * it stores its value(s) into the persistent {@link SharedPreferences}
925      * storage.
926      *
927      * @param persistent Set true if it should store its value(s) into the {@link SharedPreferences}.
928      */
setPersistent(boolean persistent)929     public void setPersistent(boolean persistent) {
930         mPersistent = persistent;
931     }
932 
933     /**
934      * Call this method after the user changes the preference, but before the
935      * internal state is set. This allows the client to ignore the user value.
936      *
937      * @param newValue The new value of this Preference.
938      * @return True if the user value should be set as the preference
939      *         value (and persisted).
940      */
callChangeListener(Object newValue)941     protected boolean callChangeListener(Object newValue) {
942         return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
943     }
944 
945     /**
946      * Sets the callback to be invoked when this Preference is changed by the
947      * user (but before the internal state has been updated).
948      *
949      * @param onPreferenceChangeListener The callback to be invoked.
950      */
setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener)951     public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
952         mOnChangeListener = onPreferenceChangeListener;
953     }
954 
955     /**
956      * Returns the callback to be invoked when this Preference is changed by the
957      * user (but before the internal state has been updated).
958      *
959      * @return The callback to be invoked.
960      */
getOnPreferenceChangeListener()961     public OnPreferenceChangeListener getOnPreferenceChangeListener() {
962         return mOnChangeListener;
963     }
964 
965     /**
966      * Sets the callback to be invoked when this Preference is clicked.
967      *
968      * @param onPreferenceClickListener The callback to be invoked.
969      */
setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener)970     public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
971         mOnClickListener = onPreferenceClickListener;
972     }
973 
974     /**
975      * Returns the callback to be invoked when this Preference is clicked.
976      *
977      * @return The callback to be invoked.
978      */
getOnPreferenceClickListener()979     public OnPreferenceClickListener getOnPreferenceClickListener() {
980         return mOnClickListener;
981     }
982 
983     /**
984      * Called when a click should be performed.
985      *
986      * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
987      *            listener should be called in the proper order (between other
988      *            processing). May be null.
989      * @hide
990      */
performClick(PreferenceScreen preferenceScreen)991     public void performClick(PreferenceScreen preferenceScreen) {
992 
993         if (!isEnabled()) {
994             return;
995         }
996 
997         onClick();
998 
999         if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
1000             return;
1001         }
1002 
1003         PreferenceManager preferenceManager = getPreferenceManager();
1004         if (preferenceManager != null) {
1005             PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
1006                     .getOnPreferenceTreeClickListener();
1007             if (preferenceScreen != null && listener != null
1008                     && listener.onPreferenceTreeClick(preferenceScreen, this)) {
1009                 return;
1010             }
1011         }
1012 
1013         if (mIntent != null) {
1014             Context context = getContext();
1015             context.startActivity(mIntent);
1016         }
1017     }
1018 
1019     /**
1020      * Allows a Preference to intercept key events without having focus.
1021      * For example, SeekBarPreference uses this to intercept +/- to adjust
1022      * the progress.
1023      * @return True if the Preference handled the key. Returns false by default.
1024      * @hide
1025      */
onKey(View v, int keyCode, KeyEvent event)1026     public boolean onKey(View v, int keyCode, KeyEvent event) {
1027         return false;
1028     }
1029 
1030     /**
1031      * Returns the {@link android.content.Context} of this Preference.
1032      * Each Preference in a Preference hierarchy can be
1033      * from different Context (for example, if multiple activities provide preferences into a single
1034      * {@link PreferenceActivity}). This Context will be used to save the Preference values.
1035      *
1036      * @return The Context of this Preference.
1037      */
getContext()1038     public Context getContext() {
1039         return mContext;
1040     }
1041 
1042     /**
1043      * Returns the {@link SharedPreferences} where this Preference can read its
1044      * value(s). Usually, it's easier to use one of the helper read methods:
1045      * {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)},
1046      * {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)},
1047      * {@link #getPersistedString(String)}. To save values, see
1048      * {@link #getEditor()}.
1049      * <p>
1050      * In some cases, writes to the {@link #getEditor()} will not be committed
1051      * right away and hence not show up in the returned
1052      * {@link SharedPreferences}, this is intended behavior to improve
1053      * performance.
1054      *
1055      * @return The {@link SharedPreferences} where this Preference reads its
1056      *         value(s), or null if it isn't attached to a Preference hierarchy.
1057      * @see #getEditor()
1058      */
getSharedPreferences()1059     public SharedPreferences getSharedPreferences() {
1060         if (mPreferenceManager == null) {
1061             return null;
1062         }
1063 
1064         return mPreferenceManager.getSharedPreferences();
1065     }
1066 
1067     /**
1068      * Returns an {@link SharedPreferences.Editor} where this Preference can
1069      * save its value(s). Usually it's easier to use one of the helper save
1070      * methods: {@link #persistBoolean(boolean)}, {@link #persistFloat(float)},
1071      * {@link #persistInt(int)}, {@link #persistLong(long)},
1072      * {@link #persistString(String)}. To read values, see
1073      * {@link #getSharedPreferences()}. If {@link #shouldCommit()} returns
1074      * true, it is this Preference's responsibility to commit.
1075      * <p>
1076      * In some cases, writes to this will not be committed right away and hence
1077      * not show up in the SharedPreferences, this is intended behavior to
1078      * improve performance.
1079      *
1080      * @return A {@link SharedPreferences.Editor} where this preference saves
1081      *         its value(s), or null if it isn't attached to a Preference
1082      *         hierarchy.
1083      * @see #shouldCommit()
1084      * @see #getSharedPreferences()
1085      */
getEditor()1086     public SharedPreferences.Editor getEditor() {
1087         if (mPreferenceManager == null) {
1088             return null;
1089         }
1090 
1091         return mPreferenceManager.getEditor();
1092     }
1093 
1094     /**
1095      * Returns whether the {@link Preference} should commit its saved value(s) in
1096      * {@link #getEditor()}. This may return false in situations where batch
1097      * committing is being done (by the manager) to improve performance.
1098      *
1099      * @return Whether the Preference should commit its saved value(s).
1100      * @see #getEditor()
1101      */
shouldCommit()1102     public boolean shouldCommit() {
1103         if (mPreferenceManager == null) {
1104             return false;
1105         }
1106 
1107         return mPreferenceManager.shouldCommit();
1108     }
1109 
1110     /**
1111      * Compares Preference objects based on order (if set), otherwise alphabetically on the titles.
1112      *
1113      * @param another The Preference to compare to this one.
1114      * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
1115      *          greater than 0 if this Preference sorts after <var>another</var>.
1116      */
1117     @Override
compareTo(Preference another)1118     public int compareTo(Preference another) {
1119         if (mOrder != another.mOrder) {
1120             // Do order comparison
1121             return mOrder - another.mOrder;
1122         } else if (mTitle == another.mTitle) {
1123             // If titles are null or share same object comparison
1124             return 0;
1125         } else if (mTitle == null) {
1126             return 1;
1127         } else if (another.mTitle == null) {
1128             return -1;
1129         } else {
1130             // Do name comparison
1131             return CharSequences.compareToIgnoreCase(mTitle, another.mTitle);
1132         }
1133     }
1134 
1135     /**
1136      * Sets the internal change listener.
1137      *
1138      * @param listener The listener.
1139      * @see #notifyChanged()
1140      */
setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener)1141     final void setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener) {
1142         mListener = listener;
1143     }
1144 
1145     /**
1146      * Should be called when the data of this {@link Preference} has changed.
1147      */
notifyChanged()1148     protected void notifyChanged() {
1149         if (mListener != null) {
1150             mListener.onPreferenceChange(this);
1151         }
1152     }
1153 
1154     /**
1155      * Should be called when a Preference has been
1156      * added/removed from this group, or the ordering should be
1157      * re-evaluated.
1158      */
notifyHierarchyChanged()1159     protected void notifyHierarchyChanged() {
1160         if (mListener != null) {
1161             mListener.onPreferenceHierarchyChange(this);
1162         }
1163     }
1164 
1165     /**
1166      * Gets the {@link PreferenceManager} that manages this Preference object's tree.
1167      *
1168      * @return The {@link PreferenceManager}.
1169      */
getPreferenceManager()1170     public PreferenceManager getPreferenceManager() {
1171         return mPreferenceManager;
1172     }
1173 
1174     /**
1175      * Called when this Preference has been attached to a Preference hierarchy.
1176      * Make sure to call the super implementation.
1177      *
1178      * @param preferenceManager The PreferenceManager of the hierarchy.
1179      */
onAttachedToHierarchy(PreferenceManager preferenceManager)1180     protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
1181         mPreferenceManager = preferenceManager;
1182 
1183         mId = preferenceManager.getNextId();
1184 
1185         dispatchSetInitialValue();
1186     }
1187 
1188     /**
1189      * Called when the Preference hierarchy has been attached to the
1190      * {@link PreferenceActivity}. This can also be called when this
1191      * Preference has been attached to a group that was already attached
1192      * to the {@link PreferenceActivity}.
1193      */
onAttachedToActivity()1194     protected void onAttachedToActivity() {
1195         // At this point, the hierarchy that this preference is in is connected
1196         // with all other preferences.
1197         registerDependency();
1198     }
1199 
registerDependency()1200     private void registerDependency() {
1201 
1202         if (TextUtils.isEmpty(mDependencyKey)) return;
1203 
1204         Preference preference = findPreferenceInHierarchy(mDependencyKey);
1205         if (preference != null) {
1206             preference.registerDependent(this);
1207         } else {
1208             throw new IllegalStateException("Dependency \"" + mDependencyKey
1209                     + "\" not found for preference \"" + mKey + "\" (title: \"" + mTitle + "\"");
1210         }
1211     }
1212 
unregisterDependency()1213     private void unregisterDependency() {
1214         if (mDependencyKey != null) {
1215             final Preference oldDependency = findPreferenceInHierarchy(mDependencyKey);
1216             if (oldDependency != null) {
1217                 oldDependency.unregisterDependent(this);
1218             }
1219         }
1220     }
1221 
1222     /**
1223      * Finds a Preference in this hierarchy (the whole thing,
1224      * even above/below your {@link PreferenceScreen} screen break) with the given
1225      * key.
1226      * <p>
1227      * This only functions after we have been attached to a hierarchy.
1228      *
1229      * @param key The key of the Preference to find.
1230      * @return The Preference that uses the given key.
1231      */
findPreferenceInHierarchy(String key)1232     protected Preference findPreferenceInHierarchy(String key) {
1233         if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
1234             return null;
1235         }
1236 
1237         return mPreferenceManager.findPreference(key);
1238     }
1239 
1240     /**
1241      * Adds a dependent Preference on this Preference so we can notify it.
1242      * Usually, the dependent Preference registers itself (it's good for it to
1243      * know it depends on something), so please use
1244      * {@link Preference#setDependency(String)} on the dependent Preference.
1245      *
1246      * @param dependent The dependent Preference that will be enabled/disabled
1247      *            according to the state of this Preference.
1248      */
registerDependent(Preference dependent)1249     private void registerDependent(Preference dependent) {
1250         if (mDependents == null) {
1251             mDependents = new ArrayList<Preference>();
1252         }
1253 
1254         mDependents.add(dependent);
1255 
1256         dependent.onDependencyChanged(this, shouldDisableDependents());
1257     }
1258 
1259     /**
1260      * Removes a dependent Preference on this Preference.
1261      *
1262      * @param dependent The dependent Preference that will be enabled/disabled
1263      *            according to the state of this Preference.
1264      * @return Returns the same Preference object, for chaining multiple calls
1265      *         into a single statement.
1266      */
unregisterDependent(Preference dependent)1267     private void unregisterDependent(Preference dependent) {
1268         if (mDependents != null) {
1269             mDependents.remove(dependent);
1270         }
1271     }
1272 
1273     /**
1274      * Notifies any listening dependents of a change that affects the
1275      * dependency.
1276      *
1277      * @param disableDependents Whether this Preference should disable
1278      *            its dependents.
1279      */
notifyDependencyChange(boolean disableDependents)1280     public void notifyDependencyChange(boolean disableDependents) {
1281         final List<Preference> dependents = mDependents;
1282 
1283         if (dependents == null) {
1284             return;
1285         }
1286 
1287         final int dependentsCount = dependents.size();
1288         for (int i = 0; i < dependentsCount; i++) {
1289             dependents.get(i).onDependencyChanged(this, disableDependents);
1290         }
1291     }
1292 
1293     /**
1294      * Called when the dependency changes.
1295      *
1296      * @param dependency The Preference that this Preference depends on.
1297      * @param disableDependent Set true to disable this Preference.
1298      */
onDependencyChanged(Preference dependency, boolean disableDependent)1299     public void onDependencyChanged(Preference dependency, boolean disableDependent) {
1300         if (mDependencyMet == disableDependent) {
1301             mDependencyMet = !disableDependent;
1302 
1303             // Enabled state can change dependent preferences' states, so notify
1304             notifyDependencyChange(shouldDisableDependents());
1305 
1306             notifyChanged();
1307         }
1308     }
1309 
1310     /**
1311      * Called when the implicit parent dependency changes.
1312      *
1313      * @param parent The Preference that this Preference depends on.
1314      * @param disableChild Set true to disable this Preference.
1315      */
onParentChanged(Preference parent, boolean disableChild)1316     public void onParentChanged(Preference parent, boolean disableChild) {
1317         if (mParentDependencyMet == disableChild) {
1318             mParentDependencyMet = !disableChild;
1319 
1320             // Enabled state can change dependent preferences' states, so notify
1321             notifyDependencyChange(shouldDisableDependents());
1322 
1323             notifyChanged();
1324         }
1325     }
1326 
1327     /**
1328      * Checks whether this preference's dependents should currently be
1329      * disabled.
1330      *
1331      * @return True if the dependents should be disabled, otherwise false.
1332      */
shouldDisableDependents()1333     public boolean shouldDisableDependents() {
1334         return !isEnabled();
1335     }
1336 
1337     /**
1338      * Sets the key of a Preference that this Preference will depend on. If that
1339      * Preference is not set or is off, this Preference will be disabled.
1340      *
1341      * @param dependencyKey The key of the Preference that this depends on.
1342      */
setDependency(String dependencyKey)1343     public void setDependency(String dependencyKey) {
1344         // Unregister the old dependency, if we had one
1345         unregisterDependency();
1346 
1347         // Register the new
1348         mDependencyKey = dependencyKey;
1349         registerDependency();
1350     }
1351 
1352     /**
1353      * Returns the key of the dependency on this Preference.
1354      *
1355      * @return The key of the dependency.
1356      * @see #setDependency(String)
1357      */
getDependency()1358     public String getDependency() {
1359         return mDependencyKey;
1360     }
1361 
1362     /**
1363      * Called when this Preference is being removed from the hierarchy. You
1364      * should remove any references to this Preference that you know about. Make
1365      * sure to call through to the superclass implementation.
1366      */
1367     @CallSuper
onPrepareForRemoval()1368     protected void onPrepareForRemoval() {
1369         unregisterDependency();
1370     }
1371 
1372     /**
1373      * Sets the default value for this Preference, which will be set either if
1374      * persistence is off or persistence is on and the preference is not found
1375      * in the persistent storage.
1376      *
1377      * @param defaultValue The default value.
1378      */
setDefaultValue(Object defaultValue)1379     public void setDefaultValue(Object defaultValue) {
1380         mDefaultValue = defaultValue;
1381     }
1382 
dispatchSetInitialValue()1383     private void dispatchSetInitialValue() {
1384         // By now, we know if we are persistent.
1385         final boolean shouldPersist = shouldPersist();
1386         if (!shouldPersist || !getSharedPreferences().contains(mKey)) {
1387             if (mDefaultValue != null) {
1388                 onSetInitialValue(false, mDefaultValue);
1389             }
1390         } else {
1391             onSetInitialValue(true, null);
1392         }
1393     }
1394 
1395     /**
1396      * Implement this to set the initial value of the Preference.
1397      * <p>
1398      * If <var>restorePersistedValue</var> is true, you should restore the
1399      * Preference value from the {@link android.content.SharedPreferences}. If
1400      * <var>restorePersistedValue</var> is false, you should set the Preference
1401      * value to defaultValue that is given (and possibly store to SharedPreferences
1402      * if {@link #shouldPersist()} is true).
1403      * <p>
1404      * This may not always be called. One example is if it should not persist
1405      * but there is no default value given.
1406      *
1407      * @param restorePersistedValue True to restore the persisted value;
1408      *            false to use the given <var>defaultValue</var>.
1409      * @param defaultValue The default value for this Preference. Only use this
1410      *            if <var>restorePersistedValue</var> is false.
1411      */
onSetInitialValue(boolean restorePersistedValue, Object defaultValue)1412     protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
1413     }
1414 
tryCommit(SharedPreferences.Editor editor)1415     private void tryCommit(SharedPreferences.Editor editor) {
1416         if (mPreferenceManager.shouldCommit()) {
1417             try {
1418                 editor.apply();
1419             } catch (AbstractMethodError unused) {
1420                 // The app injected its own pre-Gingerbread
1421                 // SharedPreferences.Editor implementation without
1422                 // an apply method.
1423                 editor.commit();
1424             }
1425         }
1426     }
1427 
1428     /**
1429      * Attempts to persist a String to the {@link android.content.SharedPreferences}.
1430      * <p>
1431      * This will check if this Preference is persistent, get an editor from
1432      * the {@link PreferenceManager}, put in the string, and check if we should commit (and
1433      * commit if so).
1434      *
1435      * @param value The value to persist.
1436      * @return True if the Preference is persistent. (This is not whether the
1437      *         value was persisted, since we may not necessarily commit if there
1438      *         will be a batch commit later.)
1439      * @see #getPersistedString(String)
1440      */
persistString(String value)1441     protected boolean persistString(String value) {
1442         if (shouldPersist()) {
1443             // Shouldn't store null
1444             if (TextUtils.equals(value, getPersistedString(null))) {
1445                 // It's already there, so the same as persisting
1446                 return true;
1447             }
1448 
1449             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1450             editor.putString(mKey, value);
1451             tryCommit(editor);
1452             return true;
1453         }
1454         return false;
1455     }
1456 
1457     /**
1458      * Attempts to get a persisted String from the {@link android.content.SharedPreferences}.
1459      * <p>
1460      * This will check if this Preference is persistent, get the SharedPreferences
1461      * from the {@link PreferenceManager}, and get the value.
1462      *
1463      * @param defaultReturnValue The default value to return if either the
1464      *            Preference is not persistent or the Preference is not in the
1465      *            shared preferences.
1466      * @return The value from the SharedPreferences or the default return
1467      *         value.
1468      * @see #persistString(String)
1469      */
getPersistedString(String defaultReturnValue)1470     protected String getPersistedString(String defaultReturnValue) {
1471         if (!shouldPersist()) {
1472             return defaultReturnValue;
1473         }
1474 
1475         return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
1476     }
1477 
1478     /**
1479      * Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}.
1480      * <p>
1481      * This will check if this Preference is persistent, get an editor from
1482      * the {@link PreferenceManager}, put in the strings, and check if we should commit (and
1483      * commit if so).
1484      *
1485      * @param values The values to persist.
1486      * @return True if the Preference is persistent. (This is not whether the
1487      *         value was persisted, since we may not necessarily commit if there
1488      *         will be a batch commit later.)
1489      * @see #getPersistedStringSet(Set)
1490      */
persistStringSet(Set<String> values)1491     public boolean persistStringSet(Set<String> values) {
1492         if (shouldPersist()) {
1493             // Shouldn't store null
1494             if (values.equals(getPersistedStringSet(null))) {
1495                 // It's already there, so the same as persisting
1496                 return true;
1497             }
1498 
1499             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1500             editor.putStringSet(mKey, values);
1501             tryCommit(editor);
1502             return true;
1503         }
1504         return false;
1505     }
1506 
1507     /**
1508      * Attempts to get a persisted set of Strings from the
1509      * {@link android.content.SharedPreferences}.
1510      * <p>
1511      * This will check if this Preference is persistent, get the SharedPreferences
1512      * from the {@link PreferenceManager}, and get the value.
1513      *
1514      * @param defaultReturnValue The default value to return if either the
1515      *            Preference is not persistent or the Preference is not in the
1516      *            shared preferences.
1517      * @return The value from the SharedPreferences or the default return
1518      *         value.
1519      * @see #persistStringSet(Set)
1520      */
getPersistedStringSet(Set<String> defaultReturnValue)1521     public Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
1522         if (!shouldPersist()) {
1523             return defaultReturnValue;
1524         }
1525 
1526         return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
1527     }
1528 
1529     /**
1530      * Attempts to persist an int to the {@link android.content.SharedPreferences}.
1531      *
1532      * @param value The value to persist.
1533      * @return True if the Preference is persistent. (This is not whether the
1534      *         value was persisted, since we may not necessarily commit if there
1535      *         will be a batch commit later.)
1536      * @see #persistString(String)
1537      * @see #getPersistedInt(int)
1538      */
persistInt(int value)1539     protected boolean persistInt(int value) {
1540         if (shouldPersist()) {
1541             if (value == getPersistedInt(~value)) {
1542                 // It's already there, so the same as persisting
1543                 return true;
1544             }
1545 
1546             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1547             editor.putInt(mKey, value);
1548             tryCommit(editor);
1549             return true;
1550         }
1551         return false;
1552     }
1553 
1554     /**
1555      * Attempts to get a persisted int from the {@link android.content.SharedPreferences}.
1556      *
1557      * @param defaultReturnValue The default value to return if either this
1558      *            Preference is not persistent or this Preference is not in the
1559      *            SharedPreferences.
1560      * @return The value from the SharedPreferences or the default return
1561      *         value.
1562      * @see #getPersistedString(String)
1563      * @see #persistInt(int)
1564      */
getPersistedInt(int defaultReturnValue)1565     protected int getPersistedInt(int defaultReturnValue) {
1566         if (!shouldPersist()) {
1567             return defaultReturnValue;
1568         }
1569 
1570         return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue);
1571     }
1572 
1573     /**
1574      * Attempts to persist a float to the {@link android.content.SharedPreferences}.
1575      *
1576      * @param value The value to persist.
1577      * @return True if this Preference is persistent. (This is not whether the
1578      *         value was persisted, since we may not necessarily commit if there
1579      *         will be a batch commit later.)
1580      * @see #persistString(String)
1581      * @see #getPersistedFloat(float)
1582      */
persistFloat(float value)1583     protected boolean persistFloat(float value) {
1584         if (shouldPersist()) {
1585             if (value == getPersistedFloat(Float.NaN)) {
1586                 // It's already there, so the same as persisting
1587                 return true;
1588             }
1589 
1590             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1591             editor.putFloat(mKey, value);
1592             tryCommit(editor);
1593             return true;
1594         }
1595         return false;
1596     }
1597 
1598     /**
1599      * Attempts to get a persisted float from the {@link android.content.SharedPreferences}.
1600      *
1601      * @param defaultReturnValue The default value to return if either this
1602      *            Preference is not persistent or this Preference is not in the
1603      *            SharedPreferences.
1604      * @return The value from the SharedPreferences or the default return
1605      *         value.
1606      * @see #getPersistedString(String)
1607      * @see #persistFloat(float)
1608      */
getPersistedFloat(float defaultReturnValue)1609     protected float getPersistedFloat(float defaultReturnValue) {
1610         if (!shouldPersist()) {
1611             return defaultReturnValue;
1612         }
1613 
1614         return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue);
1615     }
1616 
1617     /**
1618      * Attempts to persist a long to the {@link android.content.SharedPreferences}.
1619      *
1620      * @param value The value to persist.
1621      * @return True if this Preference is persistent. (This is not whether the
1622      *         value was persisted, since we may not necessarily commit if there
1623      *         will be a batch commit later.)
1624      * @see #persistString(String)
1625      * @see #getPersistedLong(long)
1626      */
persistLong(long value)1627     protected boolean persistLong(long value) {
1628         if (shouldPersist()) {
1629             if (value == getPersistedLong(~value)) {
1630                 // It's already there, so the same as persisting
1631                 return true;
1632             }
1633 
1634             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1635             editor.putLong(mKey, value);
1636             tryCommit(editor);
1637             return true;
1638         }
1639         return false;
1640     }
1641 
1642     /**
1643      * Attempts to get a persisted long from the {@link android.content.SharedPreferences}.
1644      *
1645      * @param defaultReturnValue The default value to return if either this
1646      *            Preference is not persistent or this Preference is not in the
1647      *            SharedPreferences.
1648      * @return The value from the SharedPreferences or the default return
1649      *         value.
1650      * @see #getPersistedString(String)
1651      * @see #persistLong(long)
1652      */
getPersistedLong(long defaultReturnValue)1653     protected long getPersistedLong(long defaultReturnValue) {
1654         if (!shouldPersist()) {
1655             return defaultReturnValue;
1656         }
1657 
1658         return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue);
1659     }
1660 
1661     /**
1662      * Attempts to persist a boolean to the {@link android.content.SharedPreferences}.
1663      *
1664      * @param value The value to persist.
1665      * @return True if this Preference is persistent. (This is not whether the
1666      *         value was persisted, since we may not necessarily commit if there
1667      *         will be a batch commit later.)
1668      * @see #persistString(String)
1669      * @see #getPersistedBoolean(boolean)
1670      */
persistBoolean(boolean value)1671     protected boolean persistBoolean(boolean value) {
1672         if (shouldPersist()) {
1673             if (value == getPersistedBoolean(!value)) {
1674                 // It's already there, so the same as persisting
1675                 return true;
1676             }
1677 
1678             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1679             editor.putBoolean(mKey, value);
1680             tryCommit(editor);
1681             return true;
1682         }
1683         return false;
1684     }
1685 
1686     /**
1687      * Attempts to get a persisted boolean from the {@link android.content.SharedPreferences}.
1688      *
1689      * @param defaultReturnValue The default value to return if either this
1690      *            Preference is not persistent or this Preference is not in the
1691      *            SharedPreferences.
1692      * @return The value from the SharedPreferences or the default return
1693      *         value.
1694      * @see #getPersistedString(String)
1695      * @see #persistBoolean(boolean)
1696      */
getPersistedBoolean(boolean defaultReturnValue)1697     protected boolean getPersistedBoolean(boolean defaultReturnValue) {
1698         if (!shouldPersist()) {
1699             return defaultReturnValue;
1700         }
1701 
1702         return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
1703     }
1704 
canRecycleLayout()1705     boolean canRecycleLayout() {
1706         return mCanRecycleLayout;
1707     }
1708 
1709     @Override
toString()1710     public String toString() {
1711         return getFilterableStringBuilder().toString();
1712     }
1713 
1714     /**
1715      * Returns the text that will be used to filter this Preference depending on
1716      * user input.
1717      * <p>
1718      * If overridding and calling through to the superclass, make sure to prepend
1719      * your additions with a space.
1720      *
1721      * @return Text as a {@link StringBuilder} that will be used to filter this
1722      *         preference. By default, this is the title and summary
1723      *         (concatenated with a space).
1724      */
getFilterableStringBuilder()1725     StringBuilder getFilterableStringBuilder() {
1726         StringBuilder sb = new StringBuilder();
1727         CharSequence title = getTitle();
1728         if (!TextUtils.isEmpty(title)) {
1729             sb.append(title).append(' ');
1730         }
1731         CharSequence summary = getSummary();
1732         if (!TextUtils.isEmpty(summary)) {
1733             sb.append(summary).append(' ');
1734         }
1735         if (sb.length() > 0) {
1736             // Drop the last space
1737             sb.setLength(sb.length() - 1);
1738         }
1739         return sb;
1740     }
1741 
1742     /**
1743      * Store this Preference hierarchy's frozen state into the given container.
1744      *
1745      * @param container The Bundle in which to save the instance of this Preference.
1746      *
1747      * @see #restoreHierarchyState
1748      * @see #onSaveInstanceState
1749      */
saveHierarchyState(Bundle container)1750     public void saveHierarchyState(Bundle container) {
1751         dispatchSaveInstanceState(container);
1752     }
1753 
1754     /**
1755      * Called by {@link #saveHierarchyState} to store the instance for this Preference and its children.
1756      * May be overridden to modify how the save happens for children. For example, some
1757      * Preference objects may want to not store an instance for their children.
1758      *
1759      * @param container The Bundle in which to save the instance of this Preference.
1760      *
1761      * @see #saveHierarchyState
1762      * @see #onSaveInstanceState
1763      */
dispatchSaveInstanceState(Bundle container)1764     void dispatchSaveInstanceState(Bundle container) {
1765         if (hasKey()) {
1766             mBaseMethodCalled = false;
1767             Parcelable state = onSaveInstanceState();
1768             if (!mBaseMethodCalled) {
1769                 throw new IllegalStateException(
1770                         "Derived class did not call super.onSaveInstanceState()");
1771             }
1772             if (state != null) {
1773                 container.putParcelable(mKey, state);
1774             }
1775         }
1776     }
1777 
1778     /**
1779      * Hook allowing a Preference to generate a representation of its internal
1780      * state that can later be used to create a new instance with that same
1781      * state. This state should only contain information that is not persistent
1782      * or can be reconstructed later.
1783      *
1784      * @return A Parcelable object containing the current dynamic state of
1785      *         this Preference, or null if there is nothing interesting to save.
1786      *         The default implementation returns null.
1787      * @see #onRestoreInstanceState
1788      * @see #saveHierarchyState
1789      */
onSaveInstanceState()1790     protected Parcelable onSaveInstanceState() {
1791         mBaseMethodCalled = true;
1792         return BaseSavedState.EMPTY_STATE;
1793     }
1794 
1795     /**
1796      * Restore this Preference hierarchy's previously saved state from the given container.
1797      *
1798      * @param container The Bundle that holds the previously saved state.
1799      *
1800      * @see #saveHierarchyState
1801      * @see #onRestoreInstanceState
1802      */
restoreHierarchyState(Bundle container)1803     public void restoreHierarchyState(Bundle container) {
1804         dispatchRestoreInstanceState(container);
1805     }
1806 
1807     /**
1808      * Called by {@link #restoreHierarchyState} to retrieve the saved state for this
1809      * Preference and its children. May be overridden to modify how restoring
1810      * happens to the children of a Preference. For example, some Preference objects may
1811      * not want to save state for their children.
1812      *
1813      * @param container The Bundle that holds the previously saved state.
1814      * @see #restoreHierarchyState
1815      * @see #onRestoreInstanceState
1816      */
dispatchRestoreInstanceState(Bundle container)1817     void dispatchRestoreInstanceState(Bundle container) {
1818         if (hasKey()) {
1819             Parcelable state = container.getParcelable(mKey);
1820             if (state != null) {
1821                 mBaseMethodCalled = false;
1822                 onRestoreInstanceState(state);
1823                 if (!mBaseMethodCalled) {
1824                     throw new IllegalStateException(
1825                             "Derived class did not call super.onRestoreInstanceState()");
1826                 }
1827             }
1828         }
1829     }
1830 
1831     /**
1832      * Hook allowing a Preference to re-apply a representation of its internal
1833      * state that had previously been generated by {@link #onSaveInstanceState}.
1834      * This function will never be called with a null state.
1835      *
1836      * @param state The saved state that had previously been returned by
1837      *            {@link #onSaveInstanceState}.
1838      * @see #onSaveInstanceState
1839      * @see #restoreHierarchyState
1840      */
onRestoreInstanceState(Parcelable state)1841     protected void onRestoreInstanceState(Parcelable state) {
1842         mBaseMethodCalled = true;
1843         if (state != BaseSavedState.EMPTY_STATE && state != null) {
1844             throw new IllegalArgumentException("Wrong state class -- expecting Preference State");
1845         }
1846     }
1847 
1848     /**
1849      * A base class for managing the instance state of a {@link Preference}.
1850      */
1851     public static class BaseSavedState extends AbsSavedState {
BaseSavedState(Parcel source)1852         public BaseSavedState(Parcel source) {
1853             super(source);
1854         }
1855 
BaseSavedState(Parcelable superState)1856         public BaseSavedState(Parcelable superState) {
1857             super(superState);
1858         }
1859 
1860         public static final Parcelable.Creator<BaseSavedState> CREATOR =
1861                 new Parcelable.Creator<BaseSavedState>() {
1862             public BaseSavedState createFromParcel(Parcel in) {
1863                 return new BaseSavedState(in);
1864             }
1865 
1866             public BaseSavedState[] newArray(int size) {
1867                 return new BaseSavedState[size];
1868             }
1869         };
1870     }
1871 
1872 }
1873