Home | History | Annotate | Download | only in preference
      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 
     17 package android.preference;
     18 
     19 import android.annotation.Nullable;
     20 import android.annotation.SystemApi;
     21 import android.annotation.XmlRes;
     22 import android.app.Activity;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.Intent;
     26 import android.content.SharedPreferences;
     27 import android.content.pm.ActivityInfo;
     28 import android.content.pm.PackageManager;
     29 import android.content.pm.PackageManager.NameNotFoundException;
     30 import android.content.pm.ResolveInfo;
     31 import android.content.res.XmlResourceParser;
     32 import android.os.Bundle;
     33 import android.util.Log;
     34 
     35 import java.util.ArrayList;
     36 import java.util.HashSet;
     37 import java.util.List;
     38 
     39 /**
     40  * Used to help create {@link Preference} hierarchies
     41  * from activities or XML.
     42  * <p>
     43  * In most cases, clients should use
     44  * {@link PreferenceActivity#addPreferencesFromIntent} or
     45  * {@link PreferenceActivity#addPreferencesFromResource(int)}.
     46  *
     47  * @see PreferenceActivity
     48  */
     49 public class PreferenceManager {
     50 
     51     private static final String TAG = "PreferenceManager";
     52 
     53     /**
     54      * The Activity meta-data key for its XML preference hierarchy.
     55      */
     56     public static final String METADATA_KEY_PREFERENCES = "android.preference";
     57 
     58     public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
     59 
     60     /**
     61      * @see #getActivity()
     62      */
     63     @Nullable
     64     private Activity mActivity;
     65 
     66     /**
     67      * Fragment that owns this instance.
     68      */
     69     @Nullable
     70     private PreferenceFragment mFragment;
     71 
     72     /**
     73      * The context to use. This should always be set.
     74      *
     75      * @see #mActivity
     76      */
     77     private Context mContext;
     78 
     79     /**
     80      * The counter for unique IDs.
     81      */
     82     private long mNextId = 0;
     83 
     84     /**
     85      * The counter for unique request codes.
     86      */
     87     private int mNextRequestCode;
     88 
     89     /**
     90      * Cached shared preferences.
     91      */
     92     @Nullable
     93     private SharedPreferences mSharedPreferences;
     94 
     95     /**
     96      * Data store to be used by the Preferences or {@code null} if
     97      * {@link android.content.SharedPreferences} should be used.
     98      */
     99     @Nullable
    100     private PreferenceDataStore mPreferenceDataStore;
    101 
    102     /**
    103      * If in no-commit mode, the shared editor to give out (which will be
    104      * committed when exiting no-commit mode).
    105      */
    106     @Nullable
    107     private SharedPreferences.Editor mEditor;
    108 
    109     /**
    110      * Blocks commits from happening on the shared editor. This is used when
    111      * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
    112      */
    113     private boolean mNoCommit;
    114 
    115     /**
    116      * The SharedPreferences name that will be used for all {@link Preference}s
    117      * managed by this instance.
    118      */
    119     private String mSharedPreferencesName;
    120 
    121     /**
    122      * The SharedPreferences mode that will be used for all {@link Preference}s
    123      * managed by this instance.
    124      */
    125     private int mSharedPreferencesMode;
    126 
    127     private static final int STORAGE_DEFAULT = 0;
    128     private static final int STORAGE_DEVICE_PROTECTED = 1;
    129     private static final int STORAGE_CREDENTIAL_PROTECTED = 2;
    130 
    131     private int mStorage = STORAGE_DEFAULT;
    132 
    133     /**
    134      * The {@link PreferenceScreen} at the root of the preference hierarchy.
    135      */
    136     @Nullable
    137     private PreferenceScreen mPreferenceScreen;
    138 
    139     /**
    140      * List of activity result listeners.
    141      */
    142     @Nullable
    143     private List<OnActivityResultListener> mActivityResultListeners;
    144 
    145     /**
    146      * List of activity stop listeners.
    147      */
    148     @Nullable
    149     private List<OnActivityStopListener> mActivityStopListeners;
    150 
    151     /**
    152      * List of activity destroy listeners.
    153      */
    154     @Nullable
    155     private List<OnActivityDestroyListener> mActivityDestroyListeners;
    156 
    157     /**
    158      * List of dialogs that should be dismissed when we receive onNewIntent in
    159      * our PreferenceActivity.
    160      */
    161     @Nullable
    162     private List<DialogInterface> mPreferencesScreens;
    163 
    164     private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
    165 
    166     /**
    167      * @hide
    168      */
    169     public PreferenceManager(Activity activity, int firstRequestCode) {
    170         mActivity = activity;
    171         mNextRequestCode = firstRequestCode;
    172 
    173         init(activity);
    174     }
    175 
    176     /**
    177      * This constructor should ONLY be used when getting default values from
    178      * an XML preference hierarchy.
    179      * <p>
    180      * The {@link PreferenceManager#PreferenceManager(Activity)}
    181      * should be used ANY time a preference will be displayed, since some preference
    182      * types need an Activity for managed queries.
    183      */
    184     /*package*/ PreferenceManager(Context context) {
    185         init(context);
    186     }
    187 
    188     private void init(Context context) {
    189         mContext = context;
    190 
    191         setSharedPreferencesName(getDefaultSharedPreferencesName(context));
    192     }
    193 
    194     /**
    195      * Sets the owning preference fragment
    196      */
    197     void setFragment(PreferenceFragment fragment) {
    198         mFragment = fragment;
    199     }
    200 
    201     /**
    202      * Returns the owning preference fragment, if any.
    203      */
    204     @Nullable
    205     PreferenceFragment getFragment() {
    206         return mFragment;
    207     }
    208 
    209     /**
    210      * Sets a {@link PreferenceDataStore} to be used by all Preferences associated with this manager
    211      * that don't have a custom {@link PreferenceDataStore} assigned via
    212      * {@link Preference#setPreferenceDataStore(PreferenceDataStore)}. Also if the data store is
    213      * set, the child preferences won't use {@link android.content.SharedPreferences} as long as
    214      * they are assigned to this manager.
    215      *
    216      * @param dataStore The {@link PreferenceDataStore} to be used by this manager.
    217      * @see Preference#setPreferenceDataStore(PreferenceDataStore)
    218      */
    219     public void setPreferenceDataStore(PreferenceDataStore dataStore) {
    220         mPreferenceDataStore = dataStore;
    221     }
    222 
    223     /**
    224      * Returns the {@link PreferenceDataStore} associated with this manager or {@code null} if
    225      * the default {@link android.content.SharedPreferences} are used instead.
    226      *
    227      * @return The {@link PreferenceDataStore} associated with this manager or {@code null} if none.
    228      * @see #setPreferenceDataStore(PreferenceDataStore)
    229      */
    230     @Nullable
    231     public PreferenceDataStore getPreferenceDataStore() {
    232         return mPreferenceDataStore;
    233     }
    234 
    235     /**
    236      * Returns a list of {@link Activity} (indirectly) that match a given
    237      * {@link Intent}.
    238      *
    239      * @param queryIntent The Intent to match.
    240      * @return The list of {@link ResolveInfo} that point to the matched
    241      *         activities.
    242      */
    243     private List<ResolveInfo> queryIntentActivities(Intent queryIntent) {
    244         return mContext.getPackageManager().queryIntentActivities(queryIntent,
    245                 PackageManager.GET_META_DATA);
    246     }
    247 
    248     /**
    249      * Inflates a preference hierarchy from the preference hierarchies of
    250      * {@link Activity Activities} that match the given {@link Intent}. An
    251      * {@link Activity} defines its preference hierarchy with meta-data using
    252      * the {@link #METADATA_KEY_PREFERENCES} key.
    253      * <p>
    254      * If a preference hierarchy is given, the new preference hierarchies will
    255      * be merged in.
    256      *
    257      * @param queryIntent The intent to match activities.
    258      * @param rootPreferences Optional existing hierarchy to merge the new
    259      *            hierarchies into.
    260      * @return The root hierarchy (if one was not provided, the new hierarchy's
    261      *         root).
    262      */
    263     PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
    264         final List<ResolveInfo> activities = queryIntentActivities(queryIntent);
    265         final HashSet<String> inflatedRes = new HashSet<String>();
    266 
    267         for (int i = activities.size() - 1; i >= 0; i--) {
    268             final ActivityInfo activityInfo = activities.get(i).activityInfo;
    269             final Bundle metaData = activityInfo.metaData;
    270 
    271             if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) {
    272                 continue;
    273             }
    274 
    275             // Need to concat the package with res ID since the same res ID
    276             // can be re-used across contexts
    277             final String uniqueResId = activityInfo.packageName + ":"
    278                     + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES);
    279 
    280             if (!inflatedRes.contains(uniqueResId)) {
    281                 inflatedRes.add(uniqueResId);
    282 
    283                 final Context context;
    284                 try {
    285                     context = mContext.createPackageContext(activityInfo.packageName, 0);
    286                 } catch (NameNotFoundException e) {
    287                     Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": "
    288                             + Log.getStackTraceString(e));
    289                     continue;
    290                 }
    291 
    292                 final PreferenceInflater inflater = new PreferenceInflater(context, this);
    293                 final XmlResourceParser parser = activityInfo.loadXmlMetaData(context
    294                         .getPackageManager(), METADATA_KEY_PREFERENCES);
    295                 rootPreferences = (PreferenceScreen) inflater
    296                         .inflate(parser, rootPreferences, true);
    297                 parser.close();
    298             }
    299         }
    300 
    301         rootPreferences.onAttachedToHierarchy(this);
    302 
    303         return rootPreferences;
    304     }
    305 
    306     /**
    307      * Inflates a preference hierarchy from XML. If a preference hierarchy is
    308      * given, the new preference hierarchies will be merged in.
    309      *
    310      * @param context The context of the resource.
    311      * @param resId The resource ID of the XML to inflate.
    312      * @param rootPreferences Optional existing hierarchy to merge the new
    313      *            hierarchies into.
    314      * @return The root hierarchy (if one was not provided, the new hierarchy's
    315      *         root).
    316      * @hide
    317      */
    318     public PreferenceScreen inflateFromResource(Context context, @XmlRes int resId,
    319             PreferenceScreen rootPreferences) {
    320         // Block commits
    321         setNoCommit(true);
    322 
    323         final PreferenceInflater inflater = new PreferenceInflater(context, this);
    324         rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true);
    325         rootPreferences.onAttachedToHierarchy(this);
    326 
    327         // Unblock commits
    328         setNoCommit(false);
    329 
    330         return rootPreferences;
    331     }
    332 
    333     public PreferenceScreen createPreferenceScreen(Context context) {
    334         final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
    335         preferenceScreen.onAttachedToHierarchy(this);
    336         return preferenceScreen;
    337     }
    338 
    339     /**
    340      * Called by a preference to get a unique ID in its hierarchy.
    341      *
    342      * @return A unique ID.
    343      */
    344     long getNextId() {
    345         synchronized (this) {
    346             return mNextId++;
    347         }
    348     }
    349 
    350     /**
    351      * Returns the current name of the SharedPreferences file that preferences managed by
    352      * this will use.
    353      *
    354      * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
    355      * @see Context#getSharedPreferences(String, int)
    356      */
    357     public String getSharedPreferencesName() {
    358         return mSharedPreferencesName;
    359     }
    360 
    361     /**
    362      * Sets the name of the SharedPreferences file that preferences managed by this
    363      * will use.
    364      *
    365      * <p>If custom {@link PreferenceDataStore} is set, this won't override its usage.
    366      *
    367      * @param sharedPreferencesName The name of the SharedPreferences file.
    368      * @see Context#getSharedPreferences(String, int)
    369      * @see #setPreferenceDataStore(PreferenceDataStore)
    370      */
    371     public void setSharedPreferencesName(String sharedPreferencesName) {
    372         mSharedPreferencesName = sharedPreferencesName;
    373         mSharedPreferences = null;
    374     }
    375 
    376     /**
    377      * Returns the current mode of the SharedPreferences file that preferences managed by
    378      * this will use.
    379      *
    380      * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
    381      * @see Context#getSharedPreferences(String, int)
    382      */
    383     public int getSharedPreferencesMode() {
    384         return mSharedPreferencesMode;
    385     }
    386 
    387     /**
    388      * Sets the mode of the SharedPreferences file that preferences managed by this
    389      * will use.
    390      *
    391      * @param sharedPreferencesMode The mode of the SharedPreferences file.
    392      * @see Context#getSharedPreferences(String, int)
    393      */
    394     public void setSharedPreferencesMode(int sharedPreferencesMode) {
    395         mSharedPreferencesMode = sharedPreferencesMode;
    396         mSharedPreferences = null;
    397     }
    398 
    399     /**
    400      * Sets the storage location used internally by this class to be the default
    401      * provided by the hosting {@link Context}.
    402      */
    403     public void setStorageDefault() {
    404         mStorage = STORAGE_DEFAULT;
    405         mSharedPreferences = null;
    406     }
    407 
    408     /**
    409      * Explicitly set the storage location used internally by this class to be
    410      * device-protected storage.
    411      * <p>
    412      * On devices with direct boot, data stored in this location is encrypted
    413      * with a key tied to the physical device, and it can be accessed
    414      * immediately after the device has booted successfully, both
    415      * <em>before and after</em> the user has authenticated with their
    416      * credentials (such as a lock pattern or PIN).
    417      * <p>
    418      * Because device-protected data is available without user authentication,
    419      * you should carefully limit the data you store using this Context. For
    420      * example, storing sensitive authentication tokens or passwords in the
    421      * device-protected area is strongly discouraged.
    422      *
    423      * @see Context#createDeviceProtectedStorageContext()
    424      */
    425     public void setStorageDeviceProtected() {
    426         mStorage = STORAGE_DEVICE_PROTECTED;
    427         mSharedPreferences = null;
    428     }
    429 
    430     /**
    431      * Explicitly set the storage location used internally by this class to be
    432      * credential-protected storage. This is the default storage area for apps
    433      * unless {@code forceDeviceProtectedStorage} was requested.
    434      * <p>
    435      * On devices with direct boot, data stored in this location is encrypted
    436      * with a key tied to user credentials, which can be accessed
    437      * <em>only after</em> the user has entered their credentials (such as a
    438      * lock pattern or PIN).
    439      *
    440      * @see Context#createCredentialProtectedStorageContext()
    441      * @hide
    442      */
    443     @SystemApi
    444     public void setStorageCredentialProtected() {
    445         mStorage = STORAGE_CREDENTIAL_PROTECTED;
    446         mSharedPreferences = null;
    447     }
    448 
    449     /**
    450      * Indicates if the storage location used internally by this class is the
    451      * default provided by the hosting {@link Context}.
    452      *
    453      * @see #setStorageDefault()
    454      * @see #setStorageDeviceProtected()
    455      */
    456     public boolean isStorageDefault() {
    457         return mStorage == STORAGE_DEFAULT;
    458     }
    459 
    460     /**
    461      * Indicates if the storage location used internally by this class is backed
    462      * by device-protected storage.
    463      *
    464      * @see #setStorageDefault()
    465      * @see #setStorageDeviceProtected()
    466      */
    467     public boolean isStorageDeviceProtected() {
    468         return mStorage == STORAGE_DEVICE_PROTECTED;
    469     }
    470 
    471     /**
    472      * Indicates if the storage location used internally by this class is backed
    473      * by credential-protected storage.
    474      *
    475      * @see #setStorageDefault()
    476      * @see #setStorageDeviceProtected()
    477      * @hide
    478      */
    479     @SystemApi
    480     public boolean isStorageCredentialProtected() {
    481         return mStorage == STORAGE_CREDENTIAL_PROTECTED;
    482     }
    483 
    484     /**
    485      * Gets a {@link SharedPreferences} instance that preferences managed by this will use.
    486      *
    487      * @return a {@link SharedPreferences} instance pointing to the file that contains the values of
    488      *         preferences that are managed by this PreferenceManager. If a
    489      *         {@link PreferenceDataStore} has been set, this method returns {@code null}.
    490      */
    491     public SharedPreferences getSharedPreferences() {
    492         if (mPreferenceDataStore != null) {
    493             return null;
    494         }
    495 
    496         if (mSharedPreferences == null) {
    497             final Context storageContext;
    498             switch (mStorage) {
    499                 case STORAGE_DEVICE_PROTECTED:
    500                     storageContext = mContext.createDeviceProtectedStorageContext();
    501                     break;
    502                 case STORAGE_CREDENTIAL_PROTECTED:
    503                     storageContext = mContext.createCredentialProtectedStorageContext();
    504                     break;
    505                 default:
    506                     storageContext = mContext;
    507                     break;
    508             }
    509 
    510             mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName,
    511                     mSharedPreferencesMode);
    512         }
    513 
    514         return mSharedPreferences;
    515     }
    516 
    517     /**
    518      * Gets a {@link SharedPreferences} instance that points to the default file that is used by
    519      * the preference framework in the given context.
    520      *
    521      * @param context The context of the preferences whose values are wanted.
    522      * @return A {@link SharedPreferences} instance that can be used to retrieve and listen
    523      *         to values of the preferences.
    524      */
    525     public static SharedPreferences getDefaultSharedPreferences(Context context) {
    526         return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
    527                 getDefaultSharedPreferencesMode());
    528     }
    529 
    530     /**
    531      * Returns the name used for storing default shared preferences.
    532      *
    533      * @see #getDefaultSharedPreferences(Context)
    534      * @see Context#getSharedPreferencesPath(String)
    535      */
    536     public static String getDefaultSharedPreferencesName(Context context) {
    537         return context.getPackageName() + "_preferences";
    538     }
    539 
    540     private static int getDefaultSharedPreferencesMode() {
    541         return Context.MODE_PRIVATE;
    542     }
    543 
    544     /**
    545      * Returns the root of the preference hierarchy managed by this class.
    546      *
    547      * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
    548      */
    549     @Nullable
    550     PreferenceScreen getPreferenceScreen() {
    551         return mPreferenceScreen;
    552     }
    553 
    554     /**
    555      * Sets the root of the preference hierarchy.
    556      *
    557      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
    558      * @return Whether the {@link PreferenceScreen} given is different than the previous.
    559      */
    560     boolean setPreferences(PreferenceScreen preferenceScreen) {
    561         if (preferenceScreen != mPreferenceScreen) {
    562             mPreferenceScreen = preferenceScreen;
    563             return true;
    564         }
    565 
    566         return false;
    567     }
    568 
    569     /**
    570      * Finds a {@link Preference} based on its key.
    571      *
    572      * @param key the key of the preference to retrieve
    573      * @return the {@link Preference} with the key, or {@code null}
    574      * @see PreferenceGroup#findPreference(CharSequence)
    575      */
    576     @Nullable
    577     public Preference findPreference(CharSequence key) {
    578         if (mPreferenceScreen == null) {
    579             return null;
    580         }
    581 
    582         return mPreferenceScreen.findPreference(key);
    583     }
    584 
    585     /**
    586      * Sets the default values from an XML preference file by reading the values defined
    587      * by each {@link Preference} item's {@code android:defaultValue} attribute. This should
    588      * be called by the application's main activity.
    589      * <p>
    590      *
    591      * @param context The context of the shared preferences.
    592      * @param resId The resource ID of the preference XML file.
    593      * @param readAgain Whether to re-read the default values.
    594      * If false, this method sets the default values only if this
    595      * method has never been called in the past (or if the
    596      * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
    597      * preferences file is false). To attempt to set the default values again
    598      * bypassing this check, set {@code readAgain} to true.
    599      *            <p class="note">
    600      *            Note: this will NOT reset preferences back to their default
    601      *            values. For that functionality, use
    602      *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
    603      *            and clear it followed by a call to this method with this
    604      *            parameter set to true.
    605      */
    606     public static void setDefaultValues(Context context, @XmlRes int resId, boolean readAgain) {
    607 
    608         // Use the default shared preferences name and mode
    609         setDefaultValues(context, getDefaultSharedPreferencesName(context),
    610                 getDefaultSharedPreferencesMode(), resId, readAgain);
    611     }
    612 
    613     /**
    614      * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
    615      * the client to provide the filename and mode of the shared preferences
    616      * file.
    617      *
    618      * @param context The context of the shared preferences.
    619      * @param sharedPreferencesName A custom name for the shared preferences file.
    620      * @param sharedPreferencesMode The file creation mode for the shared preferences file, such
    621      * as {@link android.content.Context#MODE_PRIVATE} or {@link
    622      * android.content.Context#MODE_PRIVATE}
    623      * @param resId The resource ID of the preference XML file.
    624      * @param readAgain Whether to re-read the default values.
    625      * If false, this method will set the default values only if this
    626      * method has never been called in the past (or if the
    627      * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
    628      * preferences file is false). To attempt to set the default values again
    629      * bypassing this check, set {@code readAgain} to true.
    630      *            <p class="note">
    631      *            Note: this will NOT reset preferences back to their default
    632      *            values. For that functionality, use
    633      *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
    634      *            and clear it followed by a call to this method with this
    635      *            parameter set to true.
    636      *
    637      * @see #setDefaultValues(Context, int, boolean)
    638      * @see #setSharedPreferencesName(String)
    639      * @see #setSharedPreferencesMode(int)
    640      */
    641     public static void setDefaultValues(Context context, String sharedPreferencesName,
    642             int sharedPreferencesMode, int resId, boolean readAgain) {
    643         final SharedPreferences defaultValueSp = context.getSharedPreferences(
    644                 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
    645 
    646         if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
    647             final PreferenceManager pm = new PreferenceManager(context);
    648             pm.setSharedPreferencesName(sharedPreferencesName);
    649             pm.setSharedPreferencesMode(sharedPreferencesMode);
    650             pm.inflateFromResource(context, resId, null);
    651 
    652             SharedPreferences.Editor editor =
    653                     defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
    654             try {
    655                 editor.apply();
    656             } catch (AbstractMethodError unused) {
    657                 // The app injected its own pre-Gingerbread
    658                 // SharedPreferences.Editor implementation without
    659                 // an apply method.
    660                 editor.commit();
    661             }
    662         }
    663     }
    664 
    665     /**
    666      * Returns an editor to use when modifying the shared preferences.
    667      *
    668      * <p>Do NOT commit unless {@link #shouldCommit()} returns true.
    669      *
    670      * @return an editor to use to write to shared preferences. If a {@link PreferenceDataStore}
    671      *         has been set, this method returns {@code null}.
    672      * @see #shouldCommit()
    673      */
    674     SharedPreferences.Editor getEditor() {
    675         if (mPreferenceDataStore != null) {
    676             return null;
    677         }
    678 
    679         if (mNoCommit) {
    680             if (mEditor == null) {
    681                 mEditor = getSharedPreferences().edit();
    682             }
    683 
    684             return mEditor;
    685         } else {
    686             return getSharedPreferences().edit();
    687         }
    688     }
    689 
    690     /**
    691      * Whether it is the client's responsibility to commit on the
    692      * {@link #getEditor()}. This will return false in cases where the writes
    693      * should be batched, for example when inflating preferences from XML.
    694      *
    695      * <p>If preferences are using {@link PreferenceDataStore} this value is irrelevant.
    696      *
    697      * @return Whether the client should commit.
    698      */
    699     boolean shouldCommit() {
    700         return !mNoCommit;
    701     }
    702 
    703     private void setNoCommit(boolean noCommit) {
    704         if (!noCommit && mEditor != null) {
    705             try {
    706                 mEditor.apply();
    707             } catch (AbstractMethodError unused) {
    708                 // The app injected its own pre-Gingerbread
    709                 // SharedPreferences.Editor implementation without
    710                 // an apply method.
    711                 mEditor.commit();
    712             }
    713         }
    714         mNoCommit = noCommit;
    715     }
    716 
    717     /**
    718      * Returns the activity that shows the preferences. This is useful for doing
    719      * managed queries, but in most cases the use of {@link #getContext()} is
    720      * preferred.
    721      *
    722      * <p>This will return {@code null} if this class was instantiated with a Context
    723      * instead of Activity. For example, when setting the default values.
    724      *
    725      * @return The activity that shows the preferences.
    726      * @see #mContext
    727      */
    728     @Nullable
    729     Activity getActivity() {
    730         return mActivity;
    731     }
    732 
    733     /**
    734      * Returns the context. This is preferred over {@link #getActivity()} when
    735      * possible.
    736      *
    737      * @return The context.
    738      */
    739     Context getContext() {
    740         return mContext;
    741     }
    742 
    743     /**
    744      * Registers a listener.
    745      *
    746      * @see OnActivityResultListener
    747      */
    748     void registerOnActivityResultListener(OnActivityResultListener listener) {
    749         synchronized (this) {
    750             if (mActivityResultListeners == null) {
    751                 mActivityResultListeners = new ArrayList<OnActivityResultListener>();
    752             }
    753 
    754             if (!mActivityResultListeners.contains(listener)) {
    755                 mActivityResultListeners.add(listener);
    756             }
    757         }
    758     }
    759 
    760     /**
    761      * Unregisters a listener.
    762      *
    763      * @see OnActivityResultListener
    764      */
    765     void unregisterOnActivityResultListener(OnActivityResultListener listener) {
    766         synchronized (this) {
    767             if (mActivityResultListeners != null) {
    768                 mActivityResultListeners.remove(listener);
    769             }
    770         }
    771     }
    772 
    773     /**
    774      * Called by the {@link PreferenceManager} to dispatch a subactivity result.
    775      */
    776     void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
    777         List<OnActivityResultListener> list;
    778 
    779         synchronized (this) {
    780             if (mActivityResultListeners == null) return;
    781             list = new ArrayList<OnActivityResultListener>(mActivityResultListeners);
    782         }
    783 
    784         final int N = list.size();
    785         for (int i = 0; i < N; i++) {
    786             if (list.get(i).onActivityResult(requestCode, resultCode, data)) {
    787                 break;
    788             }
    789         }
    790     }
    791 
    792     /**
    793      * Registers a listener.
    794      *
    795      * @see OnActivityStopListener
    796      * @hide
    797      */
    798     public void registerOnActivityStopListener(OnActivityStopListener listener) {
    799         synchronized (this) {
    800             if (mActivityStopListeners == null) {
    801                 mActivityStopListeners = new ArrayList<OnActivityStopListener>();
    802             }
    803 
    804             if (!mActivityStopListeners.contains(listener)) {
    805                 mActivityStopListeners.add(listener);
    806             }
    807         }
    808     }
    809 
    810     /**
    811      * Unregisters a listener.
    812      *
    813      * @see OnActivityStopListener
    814      * @hide
    815      */
    816     public void unregisterOnActivityStopListener(OnActivityStopListener listener) {
    817         synchronized (this) {
    818             if (mActivityStopListeners != null) {
    819                 mActivityStopListeners.remove(listener);
    820             }
    821         }
    822     }
    823 
    824     /**
    825      * Called by the {@link PreferenceManager} to dispatch the activity stop
    826      * event.
    827      */
    828     void dispatchActivityStop() {
    829         List<OnActivityStopListener> list;
    830 
    831         synchronized (this) {
    832             if (mActivityStopListeners == null) return;
    833             list = new ArrayList<OnActivityStopListener>(mActivityStopListeners);
    834         }
    835 
    836         final int N = list.size();
    837         for (int i = 0; i < N; i++) {
    838             list.get(i).onActivityStop();
    839         }
    840     }
    841 
    842     /**
    843      * Registers a listener.
    844      *
    845      * @see OnActivityDestroyListener
    846      */
    847     void registerOnActivityDestroyListener(OnActivityDestroyListener listener) {
    848         synchronized (this) {
    849             if (mActivityDestroyListeners == null) {
    850                 mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>();
    851             }
    852 
    853             if (!mActivityDestroyListeners.contains(listener)) {
    854                 mActivityDestroyListeners.add(listener);
    855             }
    856         }
    857     }
    858 
    859     /**
    860      * Unregisters a listener.
    861      *
    862      * @see OnActivityDestroyListener
    863      */
    864     void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) {
    865         synchronized (this) {
    866             if (mActivityDestroyListeners != null) {
    867                 mActivityDestroyListeners.remove(listener);
    868             }
    869         }
    870     }
    871 
    872     /**
    873      * Called by the {@link PreferenceManager} to dispatch the activity destroy
    874      * event.
    875      */
    876     void dispatchActivityDestroy() {
    877         List<OnActivityDestroyListener> list = null;
    878 
    879         synchronized (this) {
    880             if (mActivityDestroyListeners != null) {
    881                 list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
    882             }
    883         }
    884 
    885         if (list != null) {
    886             final int N = list.size();
    887             for (int i = 0; i < N; i++) {
    888                 list.get(i).onActivityDestroy();
    889             }
    890         }
    891 
    892         // Dismiss any PreferenceScreens still showing
    893         dismissAllScreens();
    894     }
    895 
    896     /**
    897      * Returns a request code that is unique for the activity. Each subsequent
    898      * call to this method should return another unique request code.
    899      *
    900      * @return A unique request code that will never be used by anyone other
    901      *         than the caller of this method.
    902      */
    903     int getNextRequestCode() {
    904         synchronized (this) {
    905             return mNextRequestCode++;
    906         }
    907     }
    908 
    909     void addPreferencesScreen(DialogInterface screen) {
    910         synchronized (this) {
    911 
    912             if (mPreferencesScreens == null) {
    913                 mPreferencesScreens = new ArrayList<DialogInterface>();
    914             }
    915 
    916             mPreferencesScreens.add(screen);
    917         }
    918     }
    919 
    920     void removePreferencesScreen(DialogInterface screen) {
    921         synchronized (this) {
    922 
    923             if (mPreferencesScreens == null) {
    924                 return;
    925             }
    926 
    927             mPreferencesScreens.remove(screen);
    928         }
    929     }
    930 
    931     /**
    932      * Called by {@link PreferenceActivity} to dispatch the new Intent event.
    933      *
    934      * @param intent The new Intent.
    935      */
    936     void dispatchNewIntent(Intent intent) {
    937         dismissAllScreens();
    938     }
    939 
    940     private void dismissAllScreens() {
    941         // Remove any of the previously shown preferences screens
    942         ArrayList<DialogInterface> screensToDismiss;
    943 
    944         synchronized (this) {
    945 
    946             if (mPreferencesScreens == null) {
    947                 return;
    948             }
    949 
    950             screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens);
    951             mPreferencesScreens.clear();
    952         }
    953 
    954         for (int i = screensToDismiss.size() - 1; i >= 0; i--) {
    955             screensToDismiss.get(i).dismiss();
    956         }
    957     }
    958 
    959     /**
    960      * Sets the callback to be invoked when a {@link Preference} in the
    961      * hierarchy rooted at this {@link PreferenceManager} is clicked.
    962      *
    963      * @param listener The callback to be invoked.
    964      */
    965     void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
    966         mOnPreferenceTreeClickListener = listener;
    967     }
    968 
    969     @Nullable
    970     OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
    971         return mOnPreferenceTreeClickListener;
    972     }
    973 
    974     /**
    975      * Interface definition for a callback to be invoked when a
    976      * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
    977      * clicked.
    978      *
    979      * @hide
    980      */
    981     public interface OnPreferenceTreeClickListener {
    982         /**
    983          * Called when a preference in the tree rooted at this
    984          * {@link PreferenceScreen} has been clicked.
    985          *
    986          * @param preferenceScreen The {@link PreferenceScreen} that the
    987          *        preference is located in.
    988          * @param preference The preference that was clicked.
    989          * @return Whether the click was handled.
    990          */
    991         boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
    992     }
    993 
    994     /**
    995      * Interface definition for a class that will be called when the container's activity
    996      * receives an activity result.
    997      */
    998     public interface OnActivityResultListener {
    999 
   1000         /**
   1001          * See Activity's onActivityResult.
   1002          *
   1003          * @return Whether the request code was handled (in which case
   1004          *         subsequent listeners will not be called.
   1005          */
   1006         boolean onActivityResult(int requestCode, int resultCode, Intent data);
   1007     }
   1008 
   1009     /**
   1010      * Interface definition for a class that will be called when the container's activity
   1011      * is stopped.
   1012      */
   1013     public interface OnActivityStopListener {
   1014 
   1015         /**
   1016          * See Activity's onStop.
   1017          */
   1018         void onActivityStop();
   1019     }
   1020 
   1021     /**
   1022      * Interface definition for a class that will be called when the container's activity
   1023      * is destroyed.
   1024      */
   1025     public interface OnActivityDestroyListener {
   1026 
   1027         /**
   1028          * See Activity's onDestroy.
   1029          */
   1030         void onActivityDestroy();
   1031     }
   1032 
   1033 }
   1034