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