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      */
PreferenceManager(Activity activity, int firstRequestCode)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      */
PreferenceManager(Context context)184     /*package*/ PreferenceManager(Context context) {
185         init(context);
186     }
187 
init(Context context)188     private void init(Context context) {
189         mContext = context;
190 
191         setSharedPreferencesName(getDefaultSharedPreferencesName(context));
192     }
193 
194     /**
195      * Sets the owning preference fragment
196      */
setFragment(PreferenceFragment fragment)197     void setFragment(PreferenceFragment fragment) {
198         mFragment = fragment;
199     }
200 
201     /**
202      * Returns the owning preference fragment, if any.
203      */
204     @Nullable
getFragment()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      */
setPreferenceDataStore(PreferenceDataStore dataStore)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
getPreferenceDataStore()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      */
queryIntentActivities(Intent queryIntent)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      */
inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences)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      */
inflateFromResource(Context context, @XmlRes int resId, PreferenceScreen rootPreferences)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 
createPreferenceScreen(Context context)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      */
getNextId()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      */
getSharedPreferencesName()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      */
setSharedPreferencesName(String sharedPreferencesName)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      */
getSharedPreferencesMode()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      */
setSharedPreferencesMode(int sharedPreferencesMode)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      */
setStorageDefault()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      */
setStorageDeviceProtected()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
setStorageCredentialProtected()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      */
isStorageDefault()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      */
isStorageDeviceProtected()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
isStorageCredentialProtected()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      */
getSharedPreferences()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      */
getDefaultSharedPreferences(Context context)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      */
getDefaultSharedPreferencesName(Context context)536     public static String getDefaultSharedPreferencesName(Context context) {
537         return context.getPackageName() + "_preferences";
538     }
539 
getDefaultSharedPreferencesMode()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
getPreferenceScreen()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      */
setPreferences(PreferenceScreen preferenceScreen)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
findPreference(CharSequence key)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      */
setDefaultValues(Context context, @XmlRes int resId, boolean readAgain)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      */
setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain)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      */
getEditor()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      */
shouldCommit()699     boolean shouldCommit() {
700         return !mNoCommit;
701     }
702 
setNoCommit(boolean noCommit)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
getActivity()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      */
getContext()739     Context getContext() {
740         return mContext;
741     }
742 
743     /**
744      * Registers a listener.
745      *
746      * @see OnActivityResultListener
747      */
registerOnActivityResultListener(OnActivityResultListener listener)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      */
unregisterOnActivityResultListener(OnActivityResultListener listener)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      */
dispatchActivityResult(int requestCode, int resultCode, Intent data)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      */
registerOnActivityStopListener(OnActivityStopListener listener)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      */
unregisterOnActivityStopListener(OnActivityStopListener listener)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      */
dispatchActivityStop()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      */
registerOnActivityDestroyListener(OnActivityDestroyListener listener)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      */
unregisterOnActivityDestroyListener(OnActivityDestroyListener listener)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      */
dispatchActivityDestroy()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      */
getNextRequestCode()903     int getNextRequestCode() {
904         synchronized (this) {
905             return mNextRequestCode++;
906         }
907     }
908 
addPreferencesScreen(DialogInterface screen)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 
removePreferencesScreen(DialogInterface screen)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      */
dispatchNewIntent(Intent intent)936     void dispatchNewIntent(Intent intent) {
937         dismissAllScreens();
938     }
939 
dismissAllScreens()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      */
setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener)965     void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
966         mOnPreferenceTreeClickListener = listener;
967     }
968 
969     @Nullable
getOnPreferenceTreeClickListener()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          */
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)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          */
onActivityResult(int requestCode, int resultCode, Intent data)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          */
onActivityStop()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          */
onActivityDestroy()1030         void onActivityDestroy();
1031     }
1032 
1033 }
1034