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