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
17package android.preference;
18
19import com.android.internal.util.CharSequences;
20
21import android.content.Context;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.content.res.TypedArray;
25import android.graphics.drawable.Drawable;
26import android.os.Bundle;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.text.TextUtils;
30import android.util.AttributeSet;
31import android.view.AbsSavedState;
32import android.view.KeyEvent;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.view.ViewGroup;
36import android.widget.ImageView;
37import android.widget.ListView;
38import android.widget.TextView;
39
40import java.util.ArrayList;
41import java.util.List;
42import 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 */
80public 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         */
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         */
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         */
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         */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
1364    public void setDefaultValue(Object defaultValue) {
1365        mDefaultValue = defaultValue;
1366    }
1367
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     */
1397    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
1398    }
1399
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
1686    protected boolean getPersistedBoolean(boolean defaultReturnValue) {
1687        if (!shouldPersist()) {
1688            return defaultReturnValue;
1689        }
1690
1691        return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
1692    }
1693
1694    boolean canRecycleLayout() {
1695        return mCanRecycleLayout;
1696    }
1697
1698    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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 {
1841        public BaseSavedState(Parcel source) {
1842            super(source);
1843        }
1844
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