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