Home | History | Annotate | Download | only in preference
      1 /*
      2  * Copyright 2018 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 
     17 package androidx.preference;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Build;
     25 import android.text.TextUtils;
     26 
     27 import androidx.annotation.Nullable;
     28 import androidx.annotation.RestrictTo;
     29 import androidx.core.content.ContextCompat;
     30 
     31 /**
     32  * Used to help create {@link Preference} hierarchies
     33  * from activities or XML.
     34  * <p>
     35  * In most cases, clients should use
     36  * {@link androidx.preference.PreferenceFragment#addPreferencesFromResource(int)}, or
     37  * {@link PreferenceFragmentCompat#addPreferencesFromResource(int)}.
     38  *
     39  * @see androidx.preference.PreferenceFragment
     40  * @see PreferenceFragmentCompat
     41  */
     42 public class PreferenceManager {
     43 
     44     public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
     45 
     46     /**
     47      * The context to use. This should always be set.
     48      */
     49     private Context mContext;
     50 
     51     /**
     52      * The counter for unique IDs.
     53      */
     54     private long mNextId = 0;
     55 
     56     /**
     57      * Cached shared preferences.
     58      */
     59     @Nullable
     60     private SharedPreferences mSharedPreferences;
     61 
     62     /**
     63      * Data store to be used by the Preferences or null if {@link android.content.SharedPreferences}
     64      * should be used.
     65      */
     66     @Nullable
     67     private PreferenceDataStore mPreferenceDataStore;
     68 
     69     /**
     70      * If in no-commit mode, the shared editor to give out (which will be
     71      * committed when exiting no-commit mode).
     72      */
     73     @Nullable
     74     private SharedPreferences.Editor mEditor;
     75 
     76     /**
     77      * Blocks commits from happening on the shared editor. This is used when
     78      * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
     79      */
     80     private boolean mNoCommit;
     81 
     82     /**
     83      * The SharedPreferences name that will be used for all {@link Preference}s
     84      * managed by this instance.
     85      */
     86     private String mSharedPreferencesName;
     87 
     88     /**
     89      * The SharedPreferences mode that will be used for all {@link Preference}s
     90      * managed by this instance.
     91      */
     92     private int mSharedPreferencesMode;
     93 
     94     private static final int STORAGE_DEFAULT = 0;
     95     private static final int STORAGE_DEVICE_PROTECTED = 1;
     96 
     97     private int mStorage = STORAGE_DEFAULT;
     98 
     99     /**
    100      * The {@link PreferenceScreen} at the root of the preference hierarchy.
    101      */
    102     private PreferenceScreen mPreferenceScreen;
    103 
    104     private PreferenceComparisonCallback mPreferenceComparisonCallback;
    105     private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
    106     private OnDisplayPreferenceDialogListener mOnDisplayPreferenceDialogListener;
    107     private OnNavigateToScreenListener mOnNavigateToScreenListener;
    108 
    109     /**
    110      * @hide
    111      */
    112     @RestrictTo(LIBRARY_GROUP)
    113     public PreferenceManager(Context context) {
    114         mContext = context;
    115 
    116         setSharedPreferencesName(getDefaultSharedPreferencesName(context));
    117     }
    118 
    119     /**
    120      * Inflates a preference hierarchy from XML. If a preference hierarchy is
    121      * given, the new preference hierarchies will be merged in.
    122      *
    123      * @param context The context of the resource.
    124      * @param resId The resource ID of the XML to inflate.
    125      * @param rootPreferences Optional existing hierarchy to merge the new
    126      *            hierarchies into.
    127      * @return The root hierarchy (if one was not provided, the new hierarchy's
    128      *         root).
    129      * @hide
    130      */
    131     @RestrictTo(LIBRARY_GROUP)
    132     public PreferenceScreen inflateFromResource(Context context, int resId,
    133             PreferenceScreen rootPreferences) {
    134         // Block commits
    135         setNoCommit(true);
    136 
    137         final PreferenceInflater inflater = new PreferenceInflater(context, this);
    138         rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences);
    139         rootPreferences.onAttachedToHierarchy(this);
    140 
    141         // Unblock commits
    142         setNoCommit(false);
    143 
    144         return rootPreferences;
    145     }
    146 
    147     public PreferenceScreen createPreferenceScreen(Context context) {
    148         final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
    149         preferenceScreen.onAttachedToHierarchy(this);
    150         return preferenceScreen;
    151     }
    152 
    153     /**
    154      * Called by a preference to get a unique ID in its hierarchy.
    155      *
    156      * @return A unique ID.
    157      */
    158     long getNextId() {
    159         synchronized (this) {
    160             return mNextId++;
    161         }
    162     }
    163 
    164     /**
    165      * Returns the current name of the {@link SharedPreferences} file that preferences managed by
    166      * this will use.
    167      *
    168      * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
    169      * @see Context#getSharedPreferences(String, int)
    170      */
    171     public String getSharedPreferencesName() {
    172         return mSharedPreferencesName;
    173     }
    174 
    175     /**
    176      * Sets the name of the {@link SharedPreferences} file that preferences managed by this
    177      * will use.
    178      *
    179      * <p>If custom {@link PreferenceDataStore} is set, this won't override its usage.
    180      *
    181      * @param sharedPreferencesName The name of the SharedPreferences file.
    182      * @see Context#getSharedPreferences(String, int)
    183      * @see #setPreferenceDataStore(PreferenceDataStore)
    184      */
    185     public void setSharedPreferencesName(String sharedPreferencesName) {
    186         mSharedPreferencesName = sharedPreferencesName;
    187         mSharedPreferences = null;
    188     }
    189 
    190     /**
    191      * Returns the current mode of the SharedPreferences file that preferences managed by
    192      * this will use.
    193      *
    194      * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
    195      * @see Context#getSharedPreferences(String, int)
    196      */
    197     public int getSharedPreferencesMode() {
    198         return mSharedPreferencesMode;
    199     }
    200 
    201     /**
    202      * Sets the mode of the SharedPreferences file that preferences managed by this
    203      * will use.
    204      *
    205      * @param sharedPreferencesMode The mode of the SharedPreferences file.
    206      * @see Context#getSharedPreferences(String, int)
    207      */
    208     public void setSharedPreferencesMode(int sharedPreferencesMode) {
    209         mSharedPreferencesMode = sharedPreferencesMode;
    210         mSharedPreferences = null;
    211     }
    212 
    213     /**
    214      * Sets the storage location used internally by this class to be the default
    215      * provided by the hosting {@link Context}.
    216      */
    217     public void setStorageDefault() {
    218         if (Build.VERSION.SDK_INT >= 24) {
    219             mStorage = STORAGE_DEFAULT;
    220             mSharedPreferences = null;
    221         }
    222     }
    223 
    224     /**
    225      * Explicitly set the storage location used internally by this class to be
    226      * device-protected storage.
    227      * <p>
    228      * On devices with direct boot, data stored in this location is encrypted
    229      * with a key tied to the physical device, and it can be accessed
    230      * immediately after the device has booted successfully, both
    231      * <em>before and after</em> the user has authenticated with their
    232      * credentials (such as a lock pattern or PIN).
    233      * <p>
    234      * Because device-protected data is available without user authentication,
    235      * you should carefully limit the data you store using this Context. For
    236      * example, storing sensitive authentication tokens or passwords in the
    237      * device-protected area is strongly discouraged.
    238      * <p>
    239      * Prior to API 24 this method has no effect,
    240      * since device-protected storage is not available.
    241      *
    242      * @see Context#createDeviceProtectedStorageContext()
    243      */
    244     public void setStorageDeviceProtected() {
    245         if (Build.VERSION.SDK_INT >= 24) {
    246             mStorage = STORAGE_DEVICE_PROTECTED;
    247             mSharedPreferences = null;
    248         }
    249     }
    250 
    251     /**
    252      * Indicates if the storage location used internally by this class is the
    253      * default provided by the hosting {@link Context}.
    254      *
    255      * @see #setStorageDefault()
    256      * @see #setStorageDeviceProtected()
    257      */
    258     public boolean isStorageDefault() {
    259         if (Build.VERSION.SDK_INT >= 24) {
    260             return mStorage == STORAGE_DEFAULT;
    261         } else {
    262             return true;
    263         }
    264     }
    265 
    266     /**
    267      * Indicates if the storage location used internally by this class is backed
    268      * by device-protected storage.
    269      *
    270      * @see #setStorageDefault()
    271      * @see #setStorageDeviceProtected()
    272      */
    273     public boolean isStorageDeviceProtected() {
    274         if (Build.VERSION.SDK_INT >= 24) {
    275             return mStorage == STORAGE_DEVICE_PROTECTED;
    276         } else {
    277             return false;
    278         }
    279     }
    280 
    281     /**
    282      * Sets a {@link PreferenceDataStore} to be used by all Preferences associated with this manager
    283      * that don't have a custom {@link PreferenceDataStore} assigned via
    284      * {@link Preference#setPreferenceDataStore(PreferenceDataStore)}. Also if the data store is
    285      * set, the child preferences won't use {@link android.content.SharedPreferences} as long as
    286      * they are assigned to this manager.
    287      *
    288      * @param dataStore the {@link PreferenceDataStore} to be used by this manager
    289      * @see Preference#setPreferenceDataStore(PreferenceDataStore)
    290      */
    291     public void setPreferenceDataStore(PreferenceDataStore dataStore) {
    292         mPreferenceDataStore = dataStore;
    293     }
    294 
    295     /**
    296      * Returns the {@link PreferenceDataStore} associated with this manager or {@code null} if
    297      * the default {@link android.content.SharedPreferences} are used instead.
    298      *
    299      * @return The {@link PreferenceDataStore} associated with this manager or {@code null} if none.
    300      * @see #setPreferenceDataStore(PreferenceDataStore)
    301      */
    302     @Nullable
    303     public PreferenceDataStore getPreferenceDataStore() {
    304         return mPreferenceDataStore;
    305     }
    306 
    307     /**
    308      * Gets a {@link SharedPreferences} instance that preferences managed by this will
    309      * use.
    310      *
    311      * @return a {@link SharedPreferences} instance pointing to the file that contain the values of
    312      *         preferences that are managed by this PreferenceManager. If
    313      *         a {@link PreferenceDataStore} has been set, this method returns {@code null}.
    314      */
    315     public SharedPreferences getSharedPreferences() {
    316         if (getPreferenceDataStore() != null) {
    317             return null;
    318         }
    319 
    320         if (mSharedPreferences == null) {
    321             final Context storageContext;
    322             switch (mStorage) {
    323                 case STORAGE_DEVICE_PROTECTED:
    324                     storageContext = ContextCompat.createDeviceProtectedStorageContext(mContext);
    325                     break;
    326                 default:
    327                     storageContext = mContext;
    328                     break;
    329             }
    330 
    331             mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName,
    332                     mSharedPreferencesMode);
    333         }
    334 
    335         return mSharedPreferences;
    336     }
    337 
    338     /**
    339      * Gets a SharedPreferences instance that points to the default file that is
    340      * used by the preference framework in the given context.
    341      *
    342      * @param context The context of the preferences whose values are wanted.
    343      * @return A SharedPreferences instance that can be used to retrieve and
    344      *         listen to values of the preferences.
    345      */
    346     public static SharedPreferences getDefaultSharedPreferences(Context context) {
    347         return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
    348                 getDefaultSharedPreferencesMode());
    349     }
    350 
    351     private static String getDefaultSharedPreferencesName(Context context) {
    352         return context.getPackageName() + "_preferences";
    353     }
    354 
    355     private static int getDefaultSharedPreferencesMode() {
    356         return Context.MODE_PRIVATE;
    357     }
    358 
    359     /**
    360      * Returns the root of the preference hierarchy managed by this class.
    361      *
    362      * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
    363      */
    364     public PreferenceScreen getPreferenceScreen() {
    365         return mPreferenceScreen;
    366     }
    367 
    368     /**
    369      * Sets the root of the preference hierarchy.
    370      *
    371      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
    372      * @return Whether the {@link PreferenceScreen} given is different than the previous.
    373      */
    374     public boolean setPreferences(PreferenceScreen preferenceScreen) {
    375         if (preferenceScreen != mPreferenceScreen) {
    376             if (mPreferenceScreen != null) {
    377                 mPreferenceScreen.onDetached();
    378             }
    379             mPreferenceScreen = preferenceScreen;
    380             return true;
    381         }
    382 
    383         return false;
    384     }
    385 
    386     /**
    387      * Finds a {@link Preference} based on its key.
    388      *
    389      * @param key The key of the preference to retrieve.
    390      * @return The {@link Preference} with the key, or null.
    391      * @see PreferenceGroup#findPreference(CharSequence)
    392      */
    393     public Preference findPreference(CharSequence key) {
    394         if (mPreferenceScreen == null) {
    395             return null;
    396         }
    397 
    398         return mPreferenceScreen.findPreference(key);
    399     }
    400 
    401     /**
    402      * Sets the default values from an XML preference file by reading the values defined
    403      * by each {@link Preference} item's {@code android:defaultValue} attribute. This should
    404      * be called by the application's main activity.
    405      * <p>
    406      *
    407      * @param context The context of the shared preferences.
    408      * @param resId The resource ID of the preference XML file.
    409      * @param readAgain Whether to re-read the default values.
    410      * If false, this method sets the default values only if this
    411      * method has never been called in the past (or if the
    412      * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
    413      * preferences file is false). To attempt to set the default values again
    414      * bypassing this check, set {@code readAgain} to true.
    415      *            <p class="note">
    416      *            Note: this will NOT reset preferences back to their default
    417      *            values. For that functionality, use
    418      *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
    419      *            and clear it followed by a call to this method with this
    420      *            parameter set to true.
    421      */
    422     public static void setDefaultValues(Context context, int resId, boolean readAgain) {
    423         // Use the default shared preferences name and mode
    424         setDefaultValues(context, getDefaultSharedPreferencesName(context),
    425                 getDefaultSharedPreferencesMode(), resId, readAgain);
    426     }
    427 
    428     /**
    429      * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
    430      * the client to provide the filename and mode of the shared preferences
    431      * file.
    432      *
    433      * @param context The context of the shared preferences.
    434      * @param sharedPreferencesName A custom name for the shared preferences file.
    435      * @param sharedPreferencesMode The file creation mode for the shared preferences file, such
    436      * as {@link android.content.Context#MODE_PRIVATE} or {@link
    437      * android.content.Context#MODE_PRIVATE}
    438      * @param resId The resource ID of the preference XML file.
    439      * @param readAgain Whether to re-read the default values.
    440      * If false, this method will set the default values only if this
    441      * method has never been called in the past (or if the
    442      * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
    443      * preferences file is false). To attempt to set the default values again
    444      * bypassing this check, set {@code readAgain} to true.
    445      *            <p class="note">
    446      *            Note: this will NOT reset preferences back to their default
    447      *            values. For that functionality, use
    448      *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
    449      *            and clear it followed by a call to this method with this
    450      *            parameter set to true.
    451      *
    452      * @see #setDefaultValues(Context, int, boolean)
    453      * @see #setSharedPreferencesName(String)
    454      * @see #setSharedPreferencesMode(int)
    455      */
    456     public static void setDefaultValues(Context context, String sharedPreferencesName,
    457             int sharedPreferencesMode, int resId, boolean readAgain) {
    458         final SharedPreferences defaultValueSp = context.getSharedPreferences(
    459                 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
    460 
    461         if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
    462             final PreferenceManager pm = new PreferenceManager(context);
    463             pm.setSharedPreferencesName(sharedPreferencesName);
    464             pm.setSharedPreferencesMode(sharedPreferencesMode);
    465             pm.inflateFromResource(context, resId, null);
    466 
    467             defaultValueSp.edit()
    468                     .putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true)
    469                     .apply();
    470         }
    471     }
    472 
    473     /**
    474      * Returns an editor to use when modifying the shared preferences.
    475      *
    476      * <p>Do NOT commit unless {@link #shouldCommit()} returns true.
    477      *
    478      * @return an editor to use to write to shared preferences. If a {@link PreferenceDataStore} has
    479      *         been set, this method returns {@code null}.
    480      * @see #shouldCommit()
    481      */
    482     SharedPreferences.Editor getEditor() {
    483         if (mPreferenceDataStore != null) {
    484             return null;
    485         }
    486 
    487         if (mNoCommit) {
    488             if (mEditor == null) {
    489                 mEditor = getSharedPreferences().edit();
    490             }
    491 
    492             return mEditor;
    493         } else {
    494             return getSharedPreferences().edit();
    495         }
    496     }
    497 
    498     /**
    499      * Whether it is the client's responsibility to commit on the
    500      * {@link #getEditor()}. This will return false in cases where the writes
    501      * should be batched, for example when inflating preferences from XML.
    502      *
    503      * <p>If preferences are using {@link PreferenceDataStore} this value is irrelevant.
    504      *
    505      * @return Whether the client should commit.
    506      */
    507     boolean shouldCommit() {
    508         return !mNoCommit;
    509     }
    510 
    511     private void setNoCommit(boolean noCommit) {
    512         if (!noCommit && mEditor != null) {
    513             mEditor.apply();
    514         }
    515         mNoCommit = noCommit;
    516     }
    517 
    518     /**
    519      * Returns the context.
    520      *
    521      * @return The context.
    522      */
    523     public Context getContext() {
    524         return mContext;
    525     }
    526 
    527     public PreferenceComparisonCallback getPreferenceComparisonCallback() {
    528         return mPreferenceComparisonCallback;
    529     }
    530 
    531     public void setPreferenceComparisonCallback(
    532             PreferenceComparisonCallback preferenceComparisonCallback) {
    533         mPreferenceComparisonCallback = preferenceComparisonCallback;
    534     }
    535 
    536     public OnDisplayPreferenceDialogListener getOnDisplayPreferenceDialogListener() {
    537         return mOnDisplayPreferenceDialogListener;
    538     }
    539 
    540     public void setOnDisplayPreferenceDialogListener(
    541             OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener) {
    542         mOnDisplayPreferenceDialogListener = onDisplayPreferenceDialogListener;
    543     }
    544 
    545     /**
    546      * Called when a preference requests that a dialog be shown to complete a user interaction.
    547      *
    548      * @param preference The preference requesting the dialog.
    549      */
    550     public void showDialog(Preference preference) {
    551         if (mOnDisplayPreferenceDialogListener != null) {
    552             mOnDisplayPreferenceDialogListener.onDisplayPreferenceDialog(preference);
    553         }
    554     }
    555 
    556     /**
    557      * Sets the callback to be invoked when a {@link Preference} in the
    558      * hierarchy rooted at this {@link PreferenceManager} is clicked.
    559      *
    560      * @param listener The callback to be invoked.
    561      */
    562     public void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
    563         mOnPreferenceTreeClickListener = listener;
    564     }
    565 
    566     public OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
    567         return mOnPreferenceTreeClickListener;
    568     }
    569 
    570     /**
    571      * Sets the callback to be invoked when a {@link PreferenceScreen} in the hierarchy rooted at
    572      * this {@link PreferenceManager} is clicked.
    573      *
    574      * @param listener The callback to be invoked.
    575      */
    576     public void setOnNavigateToScreenListener(OnNavigateToScreenListener listener) {
    577         mOnNavigateToScreenListener = listener;
    578     }
    579 
    580     /**
    581      * Returns the {@link PreferenceManager.OnNavigateToScreenListener}, if one has been set.
    582      */
    583     public OnNavigateToScreenListener getOnNavigateToScreenListener() {
    584         return mOnNavigateToScreenListener;
    585     }
    586 
    587     /**
    588      * Callback class to be used by the {@link androidx.recyclerview.widget.RecyclerView.Adapter}
    589      * associated with the {@link PreferenceScreen}, used to determine when two {@link Preference}
    590      * objects are semantically and visually the same.
    591      */
    592     public static abstract class PreferenceComparisonCallback {
    593         /**
    594          * Called to determine if two {@link Preference} objects represent the same item
    595          *
    596          * @param p1 {@link Preference} object to compare
    597          * @param p2 {@link Preference} object to compare
    598          * @return {@code true} if the objects represent the same item
    599          */
    600         public abstract boolean arePreferenceItemsTheSame(Preference p1, Preference p2);
    601 
    602         /**
    603          * Called to determine if two {@link Preference} objects will display the same data
    604          *
    605          * @param p1 {@link Preference} object to compare
    606          * @param p2 {@link Preference} object to compare
    607          * @return {@code true} if the objects are visually identical
    608          */
    609         public abstract boolean arePreferenceContentsTheSame(Preference p1, Preference p2);
    610     }
    611 
    612     /**
    613      * A basic implementation of {@link PreferenceComparisonCallback} suitable for use with the
    614      * default {@link Preference} classes. If the {@link PreferenceScreen} contains custom
    615      * {@link Preference} subclasses, you must override
    616      * {@link #arePreferenceContentsTheSame(Preference, Preference)}
    617      */
    618     public static class SimplePreferenceComparisonCallback extends PreferenceComparisonCallback {
    619         /**
    620          * {@inheritDoc}
    621          *
    622          * <p>This method will not be able to track replaced {@link Preference} objects if they
    623          * do not have a unique key.</p>
    624          *
    625          * @see Preference#setKey(String)
    626          */
    627         @Override
    628         public boolean arePreferenceItemsTheSame(Preference p1, Preference p2) {
    629             return p1.getId() == p2.getId();
    630         }
    631 
    632         /**
    633          * {@inheritDoc}
    634          *
    635          * <p>The result of this method is only valid for the default {@link Preference} objects,
    636          * and custom subclasses which do not override
    637          * {@link Preference#onBindViewHolder(PreferenceViewHolder)}. This method also assumes
    638          * that if a preference object is being replaced by a new instance, the old instance was
    639          * not modified after being removed from its containing {@link PreferenceGroup}.</p>
    640          */
    641         @Override
    642         public boolean arePreferenceContentsTheSame(Preference p1, Preference p2) {
    643             if (p1.getClass() != p2.getClass()) {
    644                 return false;
    645             }
    646             if (p1 == p2 && p1.wasDetached()) {
    647                 // Defensively handle the case where a preference was removed, updated and re-added.
    648                 // Hopefully this is rare.
    649                 return false;
    650             }
    651             if (!TextUtils.equals(p1.getTitle(), p2.getTitle())) {
    652                 return false;
    653             }
    654             if (!TextUtils.equals(p1.getSummary(), p2.getSummary())) {
    655                 return false;
    656             }
    657             final Drawable p1Icon = p1.getIcon();
    658             final Drawable p2Icon = p2.getIcon();
    659             if (p1Icon != p2Icon && (p1Icon == null || !p1Icon.equals(p2Icon))) {
    660                 return false;
    661             }
    662             if (p1.isEnabled() != p2.isEnabled()) {
    663                 return false;
    664             }
    665             if (p1.isSelectable() != p2.isSelectable()) {
    666                 return false;
    667             }
    668             if (p1 instanceof TwoStatePreference) {
    669                 if (((TwoStatePreference) p1).isChecked()
    670                         != ((TwoStatePreference) p2).isChecked()) {
    671                     return false;
    672                 }
    673             }
    674             if (p1 instanceof DropDownPreference && p1 != p2) {
    675                 // Different object, must re-bind spinner adapter
    676                 return false;
    677             }
    678 
    679             return true;
    680         }
    681     }
    682 
    683     /**
    684      * Interface definition for a callback to be invoked when a
    685      * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
    686      * clicked.
    687      */
    688     public interface OnPreferenceTreeClickListener {
    689         /**
    690          * Called when a preference in the tree rooted at this
    691          * {@link PreferenceScreen} has been clicked.
    692          *
    693          * @param preference The preference that was clicked.
    694          * @return Whether the click was handled.
    695          */
    696         boolean onPreferenceTreeClick(Preference preference);
    697     }
    698 
    699     /**
    700      * Interface definition for a class that will be called when a
    701      * {@link androidx.preference.Preference} requests to display a dialog.
    702      */
    703     public interface OnDisplayPreferenceDialogListener {
    704 
    705         /**
    706          * Called when a preference in the tree requests to display a dialog.
    707          *
    708          * @param preference The Preference object requesting the dialog.
    709          */
    710         void onDisplayPreferenceDialog(Preference preference);
    711     }
    712 
    713     /**
    714      * Interface definition for a class that will be called when a
    715      * {@link androidx.preference.PreferenceScreen} requests navigation.
    716      */
    717     public interface OnNavigateToScreenListener {
    718 
    719         /**
    720          * Called when a PreferenceScreen in the tree requests to navigate to its contents.
    721          *
    722          * @param preferenceScreen The PreferenceScreen requesting navigation.
    723          */
    724         void onNavigateToScreen(PreferenceScreen preferenceScreen);
    725     }
    726 
    727 }
    728