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