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