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