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        mSingleLineTitle = singleLineTitle;
938        notifyChanged();
939    }
940
941    /**
942     * Gets whether the title of this preference is constrained to a single line.
943     *
944     * @see #setSingleLineTitle(boolean)
945     * @return {@code true} if the title of this preference is constrained to a single line
946     */
947    public boolean isSingleLineTitle() {
948        return mSingleLineTitle;
949    }
950
951    /**
952     * Sets whether to reserve the space of this Preference icon view when no icon is provided.
953     *
954     * @param iconSpaceReserved set {@code true} if the space for the icon view should be reserved
955     */
956    public void setIconSpaceReserved(boolean iconSpaceReserved) {
957        mIconSpaceReserved = iconSpaceReserved;
958        notifyChanged();
959    }
960
961    /**
962     * Gets whether the space this preference icon view is reserved.
963     *
964     * @see #setIconSpaceReserved(boolean)
965     * @return {@code true} if the space of this preference icon view is reserved
966     */
967    public boolean isIconSpaceReserved() {
968        return mIconSpaceReserved;
969    }
970    /**
971     * Returns a unique ID for this Preference.  This ID should be unique across all
972     * Preference objects in a hierarchy.
973     *
974     * @return A unique ID for this Preference.
975     */
976    long getId() {
977        return mId;
978    }
979
980    /**
981     * Processes a click on the preference. This includes saving the value to
982     * the {@link SharedPreferences}. However, the overridden method should
983     * call {@link #callChangeListener(Object)} to make sure the client wants to
984     * update the preference's state with the new value.
985     */
986    protected void onClick() {
987    }
988
989    /**
990     * Sets the key for this Preference, which is used as a key to the {@link SharedPreferences} or
991     * {@link PreferenceDataStore}. This should be unique for the package.
992     *
993     * @param key The key for the preference.
994     */
995    public void setKey(String key) {
996        mKey = key;
997
998        if (mRequiresKey && !hasKey()) {
999            requireKey();
1000        }
1001    }
1002
1003    /**
1004     * Gets the key for this Preference, which is also the key used for storing values into
1005     * {@link SharedPreferences} or {@link PreferenceDataStore}.
1006     *
1007     * @return The key.
1008     */
1009    public String getKey() {
1010        return mKey;
1011    }
1012
1013    /**
1014     * Checks whether the key is present, and if it isn't throws an
1015     * exception. This should be called by subclasses that persist their preferences.
1016     *
1017     * @throws IllegalStateException If there is no key assigned.
1018     */
1019    void requireKey() {
1020        if (mKey == null) {
1021            throw new IllegalStateException("Preference does not have a key assigned.");
1022        }
1023
1024        mRequiresKey = true;
1025    }
1026
1027    /**
1028     * Checks whether this Preference has a valid key.
1029     *
1030     * @return True if the key exists and is not a blank string, false otherwise.
1031     */
1032    public boolean hasKey() {
1033        return !TextUtils.isEmpty(mKey);
1034    }
1035
1036    /**
1037     * Checks whether this Preference is persistent. If it is, it stores its value(s) into
1038     * the persistent {@link SharedPreferences} storage by default or into
1039     * {@link PreferenceDataStore} if assigned.
1040     *
1041     * @return True if it is persistent.
1042     */
1043    public boolean isPersistent() {
1044        return mPersistent;
1045    }
1046
1047    /**
1048     * Checks whether, at the given time this method is called, this Preference should store/restore
1049     * its value(s) into the {@link SharedPreferences} or into {@link PreferenceDataStore} if
1050     * assigned. This, at minimum, checks whether this Preference is persistent and it currently has
1051     * a key. Before you save/restore from the storage, check this first.
1052     *
1053     * @return True if it should persist the value.
1054     */
1055    protected boolean shouldPersist() {
1056        return mPreferenceManager != null && isPersistent() && hasKey();
1057    }
1058
1059    /**
1060     * Sets whether this Preference is persistent. When persistent, it stores its value(s) into
1061     * the persistent {@link SharedPreferences} storage by default or into
1062     * {@link PreferenceDataStore} if assigned.
1063     *
1064     * @param persistent set {@code true} if it should store its value(s) into the storage.
1065     */
1066    public void setPersistent(boolean persistent) {
1067        mPersistent = persistent;
1068    }
1069
1070    /**
1071     * Call this method after the user changes the preference, but before the
1072     * internal state is set. This allows the client to ignore the user value.
1073     *
1074     * @param newValue The new value of this Preference.
1075     * @return True if the user value should be set as the preference
1076     *         value (and persisted).
1077     */
1078    protected boolean callChangeListener(Object newValue) {
1079        return mOnChangeListener == null || mOnChangeListener.onPreferenceChange(this, newValue);
1080    }
1081
1082    /**
1083     * Sets the callback to be invoked when this Preference is changed by the
1084     * user (but before the internal state has been updated).
1085     *
1086     * @param onPreferenceChangeListener The callback to be invoked.
1087     */
1088    public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
1089        mOnChangeListener = onPreferenceChangeListener;
1090    }
1091
1092    /**
1093     * Returns the callback to be invoked when this Preference is changed by the
1094     * user (but before the internal state has been updated).
1095     *
1096     * @return The callback to be invoked.
1097     */
1098    public OnPreferenceChangeListener getOnPreferenceChangeListener() {
1099        return mOnChangeListener;
1100    }
1101
1102    /**
1103     * Sets the callback to be invoked when this Preference is clicked.
1104     *
1105     * @param onPreferenceClickListener The callback to be invoked.
1106     */
1107    public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
1108        mOnClickListener = onPreferenceClickListener;
1109    }
1110
1111    /**
1112     * Returns the callback to be invoked when this Preference is clicked.
1113     *
1114     * @return The callback to be invoked.
1115     */
1116    public OnPreferenceClickListener getOnPreferenceClickListener() {
1117        return mOnClickListener;
1118    }
1119
1120    /**
1121     * Called when a click should be performed.
1122     *
1123     * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
1124     *            listener should be called in the proper order (between other
1125     *            processing). May be {@code null}.
1126     * @hide
1127     */
1128    public void performClick(PreferenceScreen preferenceScreen) {
1129
1130        if (!isEnabled()) {
1131            return;
1132        }
1133
1134        onClick();
1135
1136        if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
1137            return;
1138        }
1139
1140        PreferenceManager preferenceManager = getPreferenceManager();
1141        if (preferenceManager != null) {
1142            PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
1143                    .getOnPreferenceTreeClickListener();
1144            if (preferenceScreen != null && listener != null
1145                    && listener.onPreferenceTreeClick(preferenceScreen, this)) {
1146                return;
1147            }
1148        }
1149
1150        if (mIntent != null) {
1151            Context context = getContext();
1152            context.startActivity(mIntent);
1153        }
1154    }
1155
1156    /**
1157     * Allows a Preference to intercept key events without having focus.
1158     * For example, SeekBarPreference uses this to intercept +/- to adjust
1159     * the progress.
1160     * @return True if the Preference handled the key. Returns false by default.
1161     * @hide
1162     */
1163    public boolean onKey(View v, int keyCode, KeyEvent event) {
1164        return false;
1165    }
1166
1167    /**
1168     * Returns the {@link android.content.Context} of this Preference.
1169     * Each Preference in a Preference hierarchy can be
1170     * from different Context (for example, if multiple activities provide preferences into a single
1171     * {@link PreferenceActivity}). This Context will be used to save the Preference values.
1172     *
1173     * @return The Context of this Preference.
1174     */
1175    public Context getContext() {
1176        return mContext;
1177    }
1178
1179    /**
1180     * Returns the {@link SharedPreferences} where this Preference can read its
1181     * value(s). Usually, it's easier to use one of the helper read methods:
1182     * {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)},
1183     * {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)},
1184     * {@link #getPersistedString(String)}. To save values, see
1185     * {@link #getEditor()}.
1186     * <p>
1187     * In some cases, writes to the {@link #getEditor()} will not be committed
1188     * right away and hence not show up in the returned
1189     * {@link SharedPreferences}, this is intended behavior to improve
1190     * performance.
1191     *
1192     * @return the {@link SharedPreferences} where this Preference reads its value(s). If
1193     *         this preference isn't attached to a Preference hierarchy or if
1194     *         a {@link PreferenceDataStore} has been set, this method returns {@code null}.
1195     * @see #getEditor()
1196     * @see #setPreferenceDataStore(PreferenceDataStore)
1197     */
1198    public SharedPreferences getSharedPreferences() {
1199        if (mPreferenceManager == null || getPreferenceDataStore() != null) {
1200            return null;
1201        }
1202
1203        return mPreferenceManager.getSharedPreferences();
1204    }
1205
1206    /**
1207     * Returns an {@link SharedPreferences.Editor} where this Preference can
1208     * save its value(s). Usually it's easier to use one of the helper save
1209     * methods: {@link #persistBoolean(boolean)}, {@link #persistFloat(float)},
1210     * {@link #persistInt(int)}, {@link #persistLong(long)},
1211     * {@link #persistString(String)}. To read values, see
1212     * {@link #getSharedPreferences()}. If {@link #shouldCommit()} returns
1213     * true, it is this Preference's responsibility to commit.
1214     * <p>
1215     * In some cases, writes to this will not be committed right away and hence
1216     * not show up in the SharedPreferences, this is intended behavior to
1217     * improve performance.
1218     *
1219     * @return a {@link SharedPreferences.Editor} where this preference saves its value(s). If
1220     *         this preference isn't attached to a Preference hierarchy or if
1221     *         a {@link PreferenceDataStore} has been set, this method returns {@code null}.
1222     * @see #shouldCommit()
1223     * @see #getSharedPreferences()
1224     * @see #setPreferenceDataStore(PreferenceDataStore)
1225     */
1226    public SharedPreferences.Editor getEditor() {
1227        if (mPreferenceManager == null || getPreferenceDataStore() != null) {
1228            return null;
1229        }
1230
1231        return mPreferenceManager.getEditor();
1232    }
1233
1234    /**
1235     * Returns whether the {@link Preference} should commit its saved value(s) in
1236     * {@link #getEditor()}. This may return false in situations where batch
1237     * committing is being done (by the manager) to improve performance.
1238     *
1239     * <p>If this preference is using {@link PreferenceDataStore} this value is irrelevant.
1240     *
1241     * @return Whether the Preference should commit its saved value(s).
1242     * @see #getEditor()
1243     */
1244    public boolean shouldCommit() {
1245        if (mPreferenceManager == null) {
1246            return false;
1247        }
1248
1249        return mPreferenceManager.shouldCommit();
1250    }
1251
1252    /**
1253     * Compares Preference objects based on order (if set), otherwise alphabetically on the titles.
1254     *
1255     * @param another The Preference to compare to this one.
1256     * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
1257     *          greater than 0 if this Preference sorts after <var>another</var>.
1258     */
1259    @Override
1260    public int compareTo(Preference another) {
1261        if (mOrder != another.mOrder) {
1262            // Do order comparison
1263            return mOrder - another.mOrder;
1264        } else if (mTitle == another.mTitle) {
1265            // If titles are null or share same object comparison
1266            return 0;
1267        } else if (mTitle == null) {
1268            return 1;
1269        } else if (another.mTitle == null) {
1270            return -1;
1271        } else {
1272            // Do name comparison
1273            return CharSequences.compareToIgnoreCase(mTitle, another.mTitle);
1274        }
1275    }
1276
1277    /**
1278     * Sets the internal change listener.
1279     *
1280     * @param listener The listener.
1281     * @see #notifyChanged()
1282     */
1283    final void setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener) {
1284        mListener = listener;
1285    }
1286
1287    /**
1288     * Should be called when the data of this {@link Preference} has changed.
1289     */
1290    protected void notifyChanged() {
1291        if (mListener != null) {
1292            mListener.onPreferenceChange(this);
1293        }
1294    }
1295
1296    /**
1297     * Should be called when a Preference has been
1298     * added/removed from this group, or the ordering should be
1299     * re-evaluated.
1300     */
1301    protected void notifyHierarchyChanged() {
1302        if (mListener != null) {
1303            mListener.onPreferenceHierarchyChange(this);
1304        }
1305    }
1306
1307    /**
1308     * Gets the {@link PreferenceManager} that manages this Preference object's tree.
1309     *
1310     * @return The {@link PreferenceManager}.
1311     */
1312    public PreferenceManager getPreferenceManager() {
1313        return mPreferenceManager;
1314    }
1315
1316    /**
1317     * Called when this Preference has been attached to a Preference hierarchy.
1318     * Make sure to call the super implementation.
1319     *
1320     * @param preferenceManager The PreferenceManager of the hierarchy.
1321     */
1322    protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
1323        mPreferenceManager = preferenceManager;
1324
1325        mId = preferenceManager.getNextId();
1326
1327        dispatchSetInitialValue();
1328    }
1329
1330    /**
1331     * Called when the Preference hierarchy has been attached to the
1332     * {@link PreferenceActivity}. This can also be called when this
1333     * Preference has been attached to a group that was already attached
1334     * to the {@link PreferenceActivity}.
1335     */
1336    protected void onAttachedToActivity() {
1337        // At this point, the hierarchy that this preference is in is connected
1338        // with all other preferences.
1339        registerDependency();
1340    }
1341
1342    /**
1343     * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set {@code null} to
1344     * remove the current parent.
1345     *
1346     * @param parentGroup Parent preference group of this Preference or {@code null} if none.
1347     */
1348    void assignParent(@Nullable PreferenceGroup parentGroup) {
1349        mParentGroup = parentGroup;
1350    }
1351
1352    private void registerDependency() {
1353
1354        if (TextUtils.isEmpty(mDependencyKey)) return;
1355
1356        Preference preference = findPreferenceInHierarchy(mDependencyKey);
1357        if (preference != null) {
1358            preference.registerDependent(this);
1359        } else {
1360            throw new IllegalStateException("Dependency \"" + mDependencyKey
1361                    + "\" not found for preference \"" + mKey + "\" (title: \"" + mTitle + "\"");
1362        }
1363    }
1364
1365    private void unregisterDependency() {
1366        if (mDependencyKey != null) {
1367            final Preference oldDependency = findPreferenceInHierarchy(mDependencyKey);
1368            if (oldDependency != null) {
1369                oldDependency.unregisterDependent(this);
1370            }
1371        }
1372    }
1373
1374    /**
1375     * Finds a Preference in this hierarchy (the whole thing,
1376     * even above/below your {@link PreferenceScreen} screen break) with the given
1377     * key.
1378     * <p>
1379     * This only functions after we have been attached to a hierarchy.
1380     *
1381     * @param key The key of the Preference to find.
1382     * @return The Preference that uses the given key.
1383     */
1384    protected Preference findPreferenceInHierarchy(String key) {
1385        if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
1386            return null;
1387        }
1388
1389        return mPreferenceManager.findPreference(key);
1390    }
1391
1392    /**
1393     * Adds a dependent Preference on this Preference so we can notify it.
1394     * Usually, the dependent Preference registers itself (it's good for it to
1395     * know it depends on something), so please use
1396     * {@link Preference#setDependency(String)} on the dependent Preference.
1397     *
1398     * @param dependent The dependent Preference that will be enabled/disabled
1399     *            according to the state of this Preference.
1400     */
1401    private void registerDependent(Preference dependent) {
1402        if (mDependents == null) {
1403            mDependents = new ArrayList<Preference>();
1404        }
1405
1406        mDependents.add(dependent);
1407
1408        dependent.onDependencyChanged(this, shouldDisableDependents());
1409    }
1410
1411    /**
1412     * Removes a dependent Preference on this Preference.
1413     *
1414     * @param dependent The dependent Preference that will be enabled/disabled
1415     *            according to the state of this Preference.
1416     * @return Returns the same Preference object, for chaining multiple calls
1417     *         into a single statement.
1418     */
1419    private void unregisterDependent(Preference dependent) {
1420        if (mDependents != null) {
1421            mDependents.remove(dependent);
1422        }
1423    }
1424
1425    /**
1426     * Notifies any listening dependents of a change that affects the
1427     * dependency.
1428     *
1429     * @param disableDependents Whether this Preference should disable
1430     *            its dependents.
1431     */
1432    public void notifyDependencyChange(boolean disableDependents) {
1433        final List<Preference> dependents = mDependents;
1434
1435        if (dependents == null) {
1436            return;
1437        }
1438
1439        final int dependentsCount = dependents.size();
1440        for (int i = 0; i < dependentsCount; i++) {
1441            dependents.get(i).onDependencyChanged(this, disableDependents);
1442        }
1443    }
1444
1445    /**
1446     * Called when the dependency changes.
1447     *
1448     * @param dependency The Preference that this Preference depends on.
1449     * @param disableDependent Set true to disable this Preference.
1450     */
1451    public void onDependencyChanged(Preference dependency, boolean disableDependent) {
1452        if (mDependencyMet == disableDependent) {
1453            mDependencyMet = !disableDependent;
1454
1455            // Enabled state can change dependent preferences' states, so notify
1456            notifyDependencyChange(shouldDisableDependents());
1457
1458            notifyChanged();
1459        }
1460    }
1461
1462    /**
1463     * Called when the implicit parent dependency changes.
1464     *
1465     * @param parent The Preference that this Preference depends on.
1466     * @param disableChild Set true to disable this Preference.
1467     */
1468    public void onParentChanged(Preference parent, boolean disableChild) {
1469        if (mParentDependencyMet == disableChild) {
1470            mParentDependencyMet = !disableChild;
1471
1472            // Enabled state can change dependent preferences' states, so notify
1473            notifyDependencyChange(shouldDisableDependents());
1474
1475            notifyChanged();
1476        }
1477    }
1478
1479    /**
1480     * Checks whether this preference's dependents should currently be
1481     * disabled.
1482     *
1483     * @return True if the dependents should be disabled, otherwise false.
1484     */
1485    public boolean shouldDisableDependents() {
1486        return !isEnabled();
1487    }
1488
1489    /**
1490     * Sets the key of a Preference that this Preference will depend on. If that
1491     * Preference is not set or is off, this Preference will be disabled.
1492     *
1493     * @param dependencyKey The key of the Preference that this depends on.
1494     */
1495    public void setDependency(String dependencyKey) {
1496        // Unregister the old dependency, if we had one
1497        unregisterDependency();
1498
1499        // Register the new
1500        mDependencyKey = dependencyKey;
1501        registerDependency();
1502    }
1503
1504    /**
1505     * Returns the key of the dependency on this Preference.
1506     *
1507     * @return The key of the dependency.
1508     * @see #setDependency(String)
1509     */
1510    public String getDependency() {
1511        return mDependencyKey;
1512    }
1513
1514    /**
1515     * Returns the {@link PreferenceGroup} which is this Preference assigned to or {@code null} if
1516     * this preference is not assigned to any group or is a root Preference.
1517     *
1518     * @return the parent PreferenceGroup or {@code null} if not attached to any
1519     */
1520    @Nullable
1521    public PreferenceGroup getParent() {
1522        return mParentGroup;
1523    }
1524
1525    /**
1526     * Called when this Preference is being removed from the hierarchy. You
1527     * should remove any references to this Preference that you know about. Make
1528     * sure to call through to the superclass implementation.
1529     */
1530    @CallSuper
1531    protected void onPrepareForRemoval() {
1532        unregisterDependency();
1533    }
1534
1535    /**
1536     * Sets the default value for this Preference, which will be set either if
1537     * persistence is off or persistence is on and the preference is not found
1538     * in the persistent storage.
1539     *
1540     * @param defaultValue The default value.
1541     */
1542    public void setDefaultValue(Object defaultValue) {
1543        mDefaultValue = defaultValue;
1544    }
1545
1546    private void dispatchSetInitialValue() {
1547        if (getPreferenceDataStore() != null) {
1548            onSetInitialValue(true, mDefaultValue);
1549            return;
1550        }
1551
1552        // By now, we know if we are persistent.
1553        final boolean shouldPersist = shouldPersist();
1554        if (!shouldPersist || !getSharedPreferences().contains(mKey)) {
1555            if (mDefaultValue != null) {
1556                onSetInitialValue(false, mDefaultValue);
1557            }
1558        } else {
1559            onSetInitialValue(true, null);
1560        }
1561    }
1562
1563    /**
1564     * Implement this to set the initial value of the Preference.
1565     *
1566     * <p>If <var>restorePersistedValue</var> is true, you should restore the
1567     * Preference value from the {@link android.content.SharedPreferences}. If
1568     * <var>restorePersistedValue</var> is false, you should set the Preference
1569     * value to defaultValue that is given (and possibly store to SharedPreferences
1570     * if {@link #shouldPersist()} is true).
1571     *
1572     * <p>In case of using {@link PreferenceDataStore}, the <var>restorePersistedValue</var> is
1573     * always {@code true}. But the default value (if provided) is set.
1574     *
1575     * <p>This may not always be called. One example is if it should not persist
1576     * but there is no default value given.
1577     *
1578     * @param restorePersistedValue True to restore the persisted value;
1579     *            false to use the given <var>defaultValue</var>.
1580     * @param defaultValue The default value for this Preference. Only use this
1581     *            if <var>restorePersistedValue</var> is false.
1582     */
1583    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
1584    }
1585
1586    private void tryCommit(SharedPreferences.Editor editor) {
1587        if (mPreferenceManager.shouldCommit()) {
1588            try {
1589                editor.apply();
1590            } catch (AbstractMethodError unused) {
1591                // The app injected its own pre-Gingerbread
1592                // SharedPreferences.Editor implementation without
1593                // an apply method.
1594                editor.commit();
1595            }
1596        }
1597    }
1598
1599    /**
1600     * Attempts to persist a String if this Preference is persistent.
1601     *
1602     * @param value The value to persist.
1603     * @return True if this Preference is persistent. (This is not whether the
1604     *         value was persisted, since we may not necessarily commit if there
1605     *         will be a batch commit later.)
1606     * @see #getPersistedString(String)
1607     */
1608    protected boolean persistString(String value) {
1609        if (!shouldPersist()) {
1610            return false;
1611        }
1612
1613        // Shouldn't store null
1614        if (TextUtils.equals(value, getPersistedString(null))) {
1615            // It's already there, so the same as persisting
1616            return true;
1617        }
1618
1619        PreferenceDataStore dataStore = getPreferenceDataStore();
1620        if (dataStore != null) {
1621            dataStore.putString(mKey, value);
1622        } else {
1623            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1624            editor.putString(mKey, value);
1625            tryCommit(editor);
1626        }
1627        return true;
1628    }
1629
1630    /**
1631     * Attempts to get a persisted String if this Preference is persistent.
1632     *
1633     * @param defaultReturnValue The default value to return if either this
1634     *            Preference is not persistent or this Preference is not present.
1635     * @return The value from the data store or the default return
1636     *         value.
1637     * @see #persistString(String)
1638     */
1639    protected String getPersistedString(String defaultReturnValue) {
1640        if (!shouldPersist()) {
1641            return defaultReturnValue;
1642        }
1643
1644        PreferenceDataStore dataStore = getPreferenceDataStore();
1645        if (dataStore != null) {
1646            return dataStore.getString(mKey, defaultReturnValue);
1647        }
1648
1649        return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
1650    }
1651
1652    /**
1653     * Attempts to persist a set of Strings if this Preference is persistent.
1654     *
1655     * @param values The values to persist.
1656     * @return True if this Preference is persistent. (This is not whether the
1657     *         value was persisted, since we may not necessarily commit if there
1658     *         will be a batch commit later.)
1659     * @see #getPersistedStringSet(Set)
1660     */
1661    public boolean persistStringSet(Set<String>  values) {
1662        if (!shouldPersist()) {
1663            return false;
1664        }
1665
1666        // Shouldn't store null
1667        if (values.equals(getPersistedStringSet(null))) {
1668            // It's already there, so the same as persisting
1669            return true;
1670        }
1671
1672        PreferenceDataStore dataStore = getPreferenceDataStore();
1673        if (dataStore != null) {
1674            dataStore.putStringSet(mKey, values);
1675        } else {
1676            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1677            editor.putStringSet(mKey, values);
1678            tryCommit(editor);
1679        }
1680        return true;
1681    }
1682
1683    /**
1684     * Attempts to get a persisted set of Strings if this Preference is persistent.
1685     *
1686     * @param defaultReturnValue The default value to return if either this
1687     *            Preference is not persistent or this Preference is not present.
1688     * @return The value from the data store or the default return
1689     *         value.
1690     * @see #persistStringSet(Set)
1691     */
1692    public Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
1693        if (!shouldPersist()) {
1694            return defaultReturnValue;
1695        }
1696
1697        PreferenceDataStore dataStore = getPreferenceDataStore();
1698        if (dataStore != null) {
1699            return dataStore.getStringSet(mKey, defaultReturnValue);
1700        }
1701
1702        return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
1703    }
1704
1705    /**
1706     * Attempts to persist an int if this Preference is persistent.
1707     *
1708     * @param value The value to persist.
1709     * @return True if this Preference is persistent. (This is not whether the
1710     *         value was persisted, since we may not necessarily commit if there
1711     *         will be a batch commit later.)
1712     * @see #persistString(String)
1713     * @see #getPersistedInt(int)
1714     */
1715    protected boolean persistInt(int value) {
1716        if (!shouldPersist()) {
1717            return false;
1718        }
1719
1720        if (value == getPersistedInt(~value)) {
1721            // It's already there, so the same as persisting
1722            return true;
1723        }
1724
1725        PreferenceDataStore dataStore = getPreferenceDataStore();
1726        if (dataStore != null) {
1727            dataStore.putInt(mKey, value);
1728        } else {
1729            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1730            editor.putInt(mKey, value);
1731            tryCommit(editor);
1732        }
1733        return true;
1734    }
1735
1736    /**
1737     * Attempts to get a persisted int if this Preference is persistent.
1738     *
1739     * @param defaultReturnValue The default value to return if either this
1740     *            Preference is not persistent or this Preference is not present.
1741     * @return The value from the data store or the default return
1742     *         value.
1743     * @see #getPersistedString(String)
1744     * @see #persistInt(int)
1745     */
1746    protected int getPersistedInt(int defaultReturnValue) {
1747        if (!shouldPersist()) {
1748            return defaultReturnValue;
1749        }
1750
1751        PreferenceDataStore dataStore = getPreferenceDataStore();
1752        if (dataStore != null) {
1753            return dataStore.getInt(mKey, defaultReturnValue);
1754        }
1755
1756        return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue);
1757    }
1758
1759    /**
1760     * Attempts to persist a long if this Preference is persistent.
1761     *
1762     * @param value The value to persist.
1763     * @return True if this Preference is persistent. (This is not whether the
1764     *         value was persisted, since we may not necessarily commit if there
1765     *         will be a batch commit later.)
1766     * @see #persistString(String)
1767     * @see #getPersistedFloat(float)
1768     */
1769    protected boolean persistFloat(float value) {
1770        if (!shouldPersist()) {
1771            return false;
1772        }
1773
1774        if (value == getPersistedFloat(Float.NaN)) {
1775            // It's already there, so the same as persisting
1776            return true;
1777        }
1778
1779        PreferenceDataStore dataStore = getPreferenceDataStore();
1780        if (dataStore != null) {
1781            dataStore.putFloat(mKey, value);
1782        } else {
1783            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1784            editor.putFloat(mKey, value);
1785            tryCommit(editor);
1786        }
1787        return true;
1788    }
1789
1790    /**
1791     * Attempts to get a persisted float if this Preference is persistent.
1792     *
1793     * @param defaultReturnValue The default value to return if either this
1794     *            Preference is not persistent or this Preference is not present.
1795     * @return The value from the data store or the default return
1796     *         value.
1797     * @see #getPersistedString(String)
1798     * @see #persistFloat(float)
1799     */
1800    protected float getPersistedFloat(float defaultReturnValue) {
1801        if (!shouldPersist()) {
1802            return defaultReturnValue;
1803        }
1804
1805        PreferenceDataStore dataStore = getPreferenceDataStore();
1806        if (dataStore != null) {
1807            return dataStore.getFloat(mKey, defaultReturnValue);
1808        }
1809
1810        return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue);
1811    }
1812
1813    /**
1814     * Attempts to persist a long if this Preference is persistent.
1815     *
1816     * @param value The value to persist.
1817     * @return True if this Preference is persistent. (This is not whether the
1818     *         value was persisted, since we may not necessarily commit if there
1819     *         will be a batch commit later.)
1820     * @see #persistString(String)
1821     * @see #getPersistedLong(long)
1822     */
1823    protected boolean persistLong(long value) {
1824        if (!shouldPersist()) {
1825            return false;
1826        }
1827
1828        if (value == getPersistedLong(~value)) {
1829            // It's already there, so the same as persisting
1830            return true;
1831        }
1832
1833        PreferenceDataStore dataStore = getPreferenceDataStore();
1834        if (dataStore != null) {
1835            dataStore.putLong(mKey, value);
1836        } else {
1837            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1838            editor.putLong(mKey, value);
1839            tryCommit(editor);
1840        }
1841        return true;
1842    }
1843
1844    /**
1845     * Attempts to get a persisted long if this Preference is persistent.
1846     *
1847     * @param defaultReturnValue The default value to return if either this
1848     *            Preference is not persistent or this Preference is not present.
1849     * @return The value from the data store or the default return
1850     *         value.
1851     * @see #getPersistedString(String)
1852     * @see #persistLong(long)
1853     */
1854    protected long getPersistedLong(long defaultReturnValue) {
1855        if (!shouldPersist()) {
1856            return defaultReturnValue;
1857        }
1858
1859        PreferenceDataStore dataStore = getPreferenceDataStore();
1860        if (dataStore != null) {
1861            return dataStore.getLong(mKey, defaultReturnValue);
1862        }
1863
1864        return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue);
1865    }
1866
1867    /**
1868     * Attempts to persist a boolean if this Preference is persistent.
1869     *
1870     * @param value The value to persist.
1871     * @return True if this Preference is persistent. (This is not whether the
1872     *         value was persisted, since we may not necessarily commit if there
1873     *         will be a batch commit later.)
1874     * @see #persistString(String)
1875     * @see #getPersistedBoolean(boolean)
1876     */
1877    protected boolean persistBoolean(boolean value) {
1878        if (!shouldPersist()) {
1879            return false;
1880        }
1881
1882        if (value == getPersistedBoolean(!value)) {
1883            // It's already there, so the same as persisting
1884            return true;
1885        }
1886
1887        PreferenceDataStore dataStore = getPreferenceDataStore();
1888        if (dataStore != null) {
1889            dataStore.putBoolean(mKey, value);
1890        } else {
1891            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
1892            editor.putBoolean(mKey, value);
1893            tryCommit(editor);
1894        }
1895        return true;
1896    }
1897
1898    /**
1899     * Attempts to get a persisted boolean if this Preference is persistent.
1900     *
1901     * @param defaultReturnValue The default value to return if either this
1902     *            Preference is not persistent or this Preference is not present.
1903     * @return The value from the data store or the default return
1904     *         value.
1905     * @see #getPersistedString(String)
1906     * @see #persistBoolean(boolean)
1907     */
1908    protected boolean getPersistedBoolean(boolean defaultReturnValue) {
1909        if (!shouldPersist()) {
1910            return defaultReturnValue;
1911        }
1912
1913        PreferenceDataStore dataStore = getPreferenceDataStore();
1914        if (dataStore != null) {
1915            return dataStore.getBoolean(mKey, defaultReturnValue);
1916        }
1917
1918        return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
1919    }
1920
1921    @Override
1922    public String toString() {
1923        return getFilterableStringBuilder().toString();
1924    }
1925
1926    /**
1927     * Returns the text that will be used to filter this Preference depending on
1928     * user input.
1929     * <p>
1930     * If overridding and calling through to the superclass, make sure to prepend
1931     * your additions with a space.
1932     *
1933     * @return Text as a {@link StringBuilder} that will be used to filter this
1934     *         preference. By default, this is the title and summary
1935     *         (concatenated with a space).
1936     */
1937    StringBuilder getFilterableStringBuilder() {
1938        StringBuilder sb = new StringBuilder();
1939        CharSequence title = getTitle();
1940        if (!TextUtils.isEmpty(title)) {
1941            sb.append(title).append(' ');
1942        }
1943        CharSequence summary = getSummary();
1944        if (!TextUtils.isEmpty(summary)) {
1945            sb.append(summary).append(' ');
1946        }
1947        if (sb.length() > 0) {
1948            // Drop the last space
1949            sb.setLength(sb.length() - 1);
1950        }
1951        return sb;
1952    }
1953
1954    /**
1955     * Store this Preference hierarchy's frozen state into the given container.
1956     *
1957     * @param container The Bundle in which to save the instance of this Preference.
1958     *
1959     * @see #restoreHierarchyState
1960     * @see #onSaveInstanceState
1961     */
1962    public void saveHierarchyState(Bundle container) {
1963        dispatchSaveInstanceState(container);
1964    }
1965
1966    /**
1967     * Called by {@link #saveHierarchyState} to store the instance for this Preference and its
1968     * children. May be overridden to modify how the save happens for children. For example, some
1969     * Preference objects may want to not store an instance for their children.
1970     *
1971     * @param container The Bundle in which to save the instance of this Preference.
1972     *
1973     * @see #saveHierarchyState
1974     * @see #onSaveInstanceState
1975     */
1976    void dispatchSaveInstanceState(Bundle container) {
1977        if (hasKey()) {
1978            mBaseMethodCalled = false;
1979            Parcelable state = onSaveInstanceState();
1980            if (!mBaseMethodCalled) {
1981                throw new IllegalStateException(
1982                        "Derived class did not call super.onSaveInstanceState()");
1983            }
1984            if (state != null) {
1985                container.putParcelable(mKey, state);
1986            }
1987        }
1988    }
1989
1990    /**
1991     * Hook allowing a Preference to generate a representation of its internal
1992     * state that can later be used to create a new instance with that same
1993     * state. This state should only contain information that is not persistent
1994     * or can be reconstructed later.
1995     *
1996     * @return A Parcelable object containing the current dynamic state of this Preference, or
1997     *         {@code null} if there is nothing interesting to save. The default implementation
1998     *         returns {@code null}.
1999     * @see #onRestoreInstanceState
2000     * @see #saveHierarchyState
2001     */
2002    protected Parcelable onSaveInstanceState() {
2003        mBaseMethodCalled = true;
2004        return BaseSavedState.EMPTY_STATE;
2005    }
2006
2007    /**
2008     * Restore this Preference hierarchy's previously saved state from the given container.
2009     *
2010     * @param container The Bundle that holds the previously saved state.
2011     *
2012     * @see #saveHierarchyState
2013     * @see #onRestoreInstanceState
2014     */
2015    public void restoreHierarchyState(Bundle container) {
2016        dispatchRestoreInstanceState(container);
2017    }
2018
2019    /**
2020     * Called by {@link #restoreHierarchyState} to retrieve the saved state for this
2021     * Preference and its children. May be overridden to modify how restoring
2022     * happens to the children of a Preference. For example, some Preference objects may
2023     * not want to save state for their children.
2024     *
2025     * @param container The Bundle that holds the previously saved state.
2026     * @see #restoreHierarchyState
2027     * @see #onRestoreInstanceState
2028     */
2029    void dispatchRestoreInstanceState(Bundle container) {
2030        if (hasKey()) {
2031            Parcelable state = container.getParcelable(mKey);
2032            if (state != null) {
2033                mBaseMethodCalled = false;
2034                onRestoreInstanceState(state);
2035                if (!mBaseMethodCalled) {
2036                    throw new IllegalStateException(
2037                            "Derived class did not call super.onRestoreInstanceState()");
2038                }
2039            }
2040        }
2041    }
2042
2043    /**
2044     * Hook allowing a Preference to re-apply a representation of its internal state that had
2045     * previously been generated by {@link #onSaveInstanceState}. This function will never be called
2046     * with a {@code null} state.
2047     *
2048     * @param state The saved state that had previously been returned by
2049     *            {@link #onSaveInstanceState}.
2050     * @see #onSaveInstanceState
2051     * @see #restoreHierarchyState
2052     */
2053    protected void onRestoreInstanceState(Parcelable state) {
2054        mBaseMethodCalled = true;
2055        if (state != BaseSavedState.EMPTY_STATE && state != null) {
2056            throw new IllegalArgumentException("Wrong state class -- expecting Preference State");
2057        }
2058    }
2059
2060    /**
2061     * A base class for managing the instance state of a {@link Preference}.
2062     */
2063    public static class BaseSavedState extends AbsSavedState {
2064        public BaseSavedState(Parcel source) {
2065            super(source);
2066        }
2067
2068        public BaseSavedState(Parcelable superState) {
2069            super(superState);
2070        }
2071
2072        public static final Parcelable.Creator<BaseSavedState> CREATOR =
2073                new Parcelable.Creator<BaseSavedState>() {
2074                    public BaseSavedState createFromParcel(Parcel in) {
2075                        return new BaseSavedState(in);
2076                    }
2077
2078                    public BaseSavedState[] newArray(int size) {
2079                        return new BaseSavedState[size];
2080                    }
2081                };
2082    }
2083
2084}
2085