• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.support.v7.preference;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.support.v4.content.ContextCompat;
22 import android.support.v4.content.SharedPreferencesCompat;
23 import android.support.v4.os.BuildCompat;
24 
25 /**
26  * Used to help create {@link Preference} hierarchies
27  * from activities or XML.
28  * <p>
29  * In most cases, clients should use
30  * {@link android.support.v14.preference.PreferenceFragment#addPreferencesFromResource(int)}, or
31  * {@link PreferenceFragmentCompat#addPreferencesFromResource(int)}.
32  *
33  * @see android.support.v14.preference.PreferenceFragment
34  * @see PreferenceFragmentCompat
35  */
36 public class PreferenceManager {
37 
38     private static final String TAG = "PreferenceManager";
39 
40     public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
41 
42     /**
43      * The context to use. This should always be set.
44      */
45     private Context mContext;
46 
47     /**
48      * The counter for unique IDs.
49      */
50     private long mNextId = 0;
51 
52     /**
53      * Cached shared preferences.
54      */
55     private SharedPreferences mSharedPreferences;
56 
57     /**
58      * If in no-commit mode, the shared editor to give out (which will be
59      * committed when exiting no-commit mode).
60      */
61     private SharedPreferences.Editor mEditor;
62 
63     /**
64      * Blocks commits from happening on the shared editor. This is used when
65      * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
66      */
67     private boolean mNoCommit;
68 
69     /**
70      * The SharedPreferences name that will be used for all {@link Preference}s
71      * managed by this instance.
72      */
73     private String mSharedPreferencesName;
74 
75     /**
76      * The SharedPreferences mode that will be used for all {@link Preference}s
77      * managed by this instance.
78      */
79     private int mSharedPreferencesMode;
80 
81     private static final int STORAGE_DEFAULT = 0;
82     private static final int STORAGE_DEVICE_PROTECTED = 1;
83 
84     private int mStorage = STORAGE_DEFAULT;
85 
86     /**
87      * The {@link PreferenceScreen} at the root of the preference hierarchy.
88      */
89     private PreferenceScreen mPreferenceScreen;
90 
91     private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
92     private OnDisplayPreferenceDialogListener mOnDisplayPreferenceDialogListener;
93     private OnNavigateToScreenListener mOnNavigateToScreenListener;
94 
95     /**
96      * @hide
97      */
PreferenceManager(Context context)98     public PreferenceManager(Context context) {
99         mContext = context;
100 
101         setSharedPreferencesName(getDefaultSharedPreferencesName(context));
102     }
103 
104     /**
105      * Inflates a preference hierarchy from XML. If a preference hierarchy is
106      * given, the new preference hierarchies will be merged in.
107      *
108      * @param context The context of the resource.
109      * @param resId The resource ID of the XML to inflate.
110      * @param rootPreferences Optional existing hierarchy to merge the new
111      *            hierarchies into.
112      * @return The root hierarchy (if one was not provided, the new hierarchy's
113      *         root).
114      * @hide
115      */
inflateFromResource(Context context, int resId, PreferenceScreen rootPreferences)116     public PreferenceScreen inflateFromResource(Context context, int resId,
117             PreferenceScreen rootPreferences) {
118         // Block commits
119         setNoCommit(true);
120 
121         final PreferenceInflater inflater = new PreferenceInflater(context, this);
122         rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences);
123         rootPreferences.onAttachedToHierarchy(this);
124 
125         // Unblock commits
126         setNoCommit(false);
127 
128         return rootPreferences;
129     }
130 
createPreferenceScreen(Context context)131     public PreferenceScreen createPreferenceScreen(Context context) {
132         final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
133         preferenceScreen.onAttachedToHierarchy(this);
134         return preferenceScreen;
135     }
136 
137     /**
138      * Called by a preference to get a unique ID in its hierarchy.
139      *
140      * @return A unique ID.
141      */
getNextId()142     long getNextId() {
143         synchronized (this) {
144             return mNextId++;
145         }
146     }
147 
148     /**
149      * Returns the current name of the SharedPreferences file that preferences managed by
150      * this will use.
151      *
152      * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
153      * @see Context#getSharedPreferences(String, int)
154      */
getSharedPreferencesName()155     public String getSharedPreferencesName() {
156         return mSharedPreferencesName;
157     }
158 
159     /**
160      * Sets the name of the SharedPreferences file that preferences managed by this
161      * will use.
162      *
163      * @param sharedPreferencesName The name of the SharedPreferences file.
164      * @see Context#getSharedPreferences(String, int)
165      */
setSharedPreferencesName(String sharedPreferencesName)166     public void setSharedPreferencesName(String sharedPreferencesName) {
167         mSharedPreferencesName = sharedPreferencesName;
168         mSharedPreferences = null;
169     }
170 
171     /**
172      * Returns the current mode of the SharedPreferences file that preferences managed by
173      * this will use.
174      *
175      * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
176      * @see Context#getSharedPreferences(String, int)
177      */
getSharedPreferencesMode()178     public int getSharedPreferencesMode() {
179         return mSharedPreferencesMode;
180     }
181 
182     /**
183      * Sets the mode of the SharedPreferences file that preferences managed by this
184      * will use.
185      *
186      * @param sharedPreferencesMode The mode of the SharedPreferences file.
187      * @see Context#getSharedPreferences(String, int)
188      */
setSharedPreferencesMode(int sharedPreferencesMode)189     public void setSharedPreferencesMode(int sharedPreferencesMode) {
190         mSharedPreferencesMode = sharedPreferencesMode;
191         mSharedPreferences = null;
192     }
193 
194     /**
195      * Sets the storage location used internally by this class to be the default
196      * provided by the hosting {@link Context}.
197      */
setStorageDefault()198     public void setStorageDefault() {
199         if (BuildCompat.isAtLeastN()) {
200             mStorage = STORAGE_DEFAULT;
201             mSharedPreferences = null;
202         }
203     }
204 
205     /**
206      * Explicitly set the storage location used internally by this class to be
207      * device-protected storage.
208      * <p>
209      * On devices with direct boot, data stored in this location is encrypted
210      * with a key tied to the physical device, and it can be accessed
211      * immediately after the device has booted successfully, both
212      * <em>before and after</em> the user has authenticated with their
213      * credentials (such as a lock pattern or PIN).
214      * <p>
215      * Because device-protected data is available without user authentication,
216      * you should carefully limit the data you store using this Context. For
217      * example, storing sensitive authentication tokens or passwords in the
218      * device-protected area is strongly discouraged.
219      * <p>
220      * Prior to {@link BuildCompat#isAtLeastN()} this method has no effect,
221      * since device-protected storage is not available.
222      *
223      * @see Context#createDeviceProtectedStorageContext()
224      */
setStorageDeviceProtected()225     public void setStorageDeviceProtected() {
226         if (BuildCompat.isAtLeastN()) {
227             mStorage = STORAGE_DEVICE_PROTECTED;
228             mSharedPreferences = null;
229         }
230     }
231 
232     /**
233      * @removed
234      * @deprecated
235      */
236     @Deprecated
setStorageDeviceEncrypted()237     public void setStorageDeviceEncrypted() {
238         setStorageDeviceProtected();
239     }
240 
241     /**
242      * Indicates if the storage location used internally by this class is the
243      * default provided by the hosting {@link Context}.
244      *
245      * @see #setStorageDefault()
246      * @see #setStorageDeviceProtected()
247      */
isStorageDefault()248     public boolean isStorageDefault() {
249         if (BuildCompat.isAtLeastN()) {
250             return mStorage == STORAGE_DEFAULT;
251         } else {
252             return true;
253         }
254     }
255 
256     /**
257      * Indicates if the storage location used internally by this class is backed
258      * by device-protected storage.
259      *
260      * @see #setStorageDefault()
261      * @see #setStorageDeviceProtected()
262      */
isStorageDeviceProtected()263     public boolean isStorageDeviceProtected() {
264         if (BuildCompat.isAtLeastN()) {
265             return mStorage == STORAGE_DEVICE_PROTECTED;
266         } else {
267             return false;
268         }
269     }
270 
271     /**
272      * Gets a SharedPreferences instance that preferences managed by this will
273      * use.
274      *
275      * @return A SharedPreferences instance pointing to the file that contains
276      *         the values of preferences that are managed by this.
277      */
getSharedPreferences()278     public SharedPreferences getSharedPreferences() {
279         if (mSharedPreferences == null) {
280             final Context storageContext;
281             switch (mStorage) {
282                 case STORAGE_DEVICE_PROTECTED:
283                     storageContext = ContextCompat.createDeviceProtectedStorageContext(mContext);
284                     break;
285                 default:
286                     storageContext = mContext;
287                     break;
288             }
289 
290             mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName,
291                     mSharedPreferencesMode);
292         }
293 
294         return mSharedPreferences;
295     }
296 
297     /**
298      * Gets a SharedPreferences instance that points to the default file that is
299      * used by the preference framework in the given context.
300      *
301      * @param context The context of the preferences whose values are wanted.
302      * @return A SharedPreferences instance that can be used to retrieve and
303      *         listen to values of the preferences.
304      */
getDefaultSharedPreferences(Context context)305     public static SharedPreferences getDefaultSharedPreferences(Context context) {
306         return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
307                 getDefaultSharedPreferencesMode());
308     }
309 
getDefaultSharedPreferencesName(Context context)310     private static String getDefaultSharedPreferencesName(Context context) {
311         return context.getPackageName() + "_preferences";
312     }
313 
getDefaultSharedPreferencesMode()314     private static int getDefaultSharedPreferencesMode() {
315         return Context.MODE_PRIVATE;
316     }
317 
318     /**
319      * Returns the root of the preference hierarchy managed by this class.
320      *
321      * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
322      */
getPreferenceScreen()323     public PreferenceScreen getPreferenceScreen() {
324         return mPreferenceScreen;
325     }
326 
327     /**
328      * Sets the root of the preference hierarchy.
329      *
330      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
331      * @return Whether the {@link PreferenceScreen} given is different than the previous.
332      */
setPreferences(PreferenceScreen preferenceScreen)333     public boolean setPreferences(PreferenceScreen preferenceScreen) {
334         if (preferenceScreen != mPreferenceScreen) {
335             if (mPreferenceScreen != null) {
336                 mPreferenceScreen.onDetached();
337             }
338             mPreferenceScreen = preferenceScreen;
339             return true;
340         }
341 
342         return false;
343     }
344 
345     /**
346      * Finds a {@link Preference} based on its key.
347      *
348      * @param key The key of the preference to retrieve.
349      * @return The {@link Preference} with the key, or null.
350      * @see PreferenceGroup#findPreference(CharSequence)
351      */
findPreference(CharSequence key)352     public Preference findPreference(CharSequence key) {
353         if (mPreferenceScreen == null) {
354             return null;
355         }
356 
357         return mPreferenceScreen.findPreference(key);
358     }
359 
360     /**
361      * Sets the default values from an XML preference file by reading the values defined
362      * by each {@link Preference} item's {@code android:defaultValue} attribute. This should
363      * be called by the application's main activity.
364      * <p>
365      *
366      * @param context The context of the shared preferences.
367      * @param resId The resource ID of the preference XML file.
368      * @param readAgain Whether to re-read the default values.
369      * If false, this method sets the default values only if this
370      * method has never been called in the past (or if the
371      * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
372      * preferences file is false). To attempt to set the default values again
373      * bypassing this check, set {@code readAgain} to true.
374      *            <p class="note">
375      *            Note: this will NOT reset preferences back to their default
376      *            values. For that functionality, use
377      *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
378      *            and clear it followed by a call to this method with this
379      *            parameter set to true.
380      */
setDefaultValues(Context context, int resId, boolean readAgain)381     public static void setDefaultValues(Context context, int resId, boolean readAgain) {
382 
383         // Use the default shared preferences name and mode
384         setDefaultValues(context, getDefaultSharedPreferencesName(context),
385                 getDefaultSharedPreferencesMode(), resId, readAgain);
386     }
387 
388     /**
389      * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
390      * the client to provide the filename and mode of the shared preferences
391      * file.
392      *
393      * @param context The context of the shared preferences.
394      * @param sharedPreferencesName A custom name for the shared preferences file.
395      * @param sharedPreferencesMode The file creation mode for the shared preferences file, such
396      * as {@link android.content.Context#MODE_PRIVATE} or {@link
397      * android.content.Context#MODE_PRIVATE}
398      * @param resId The resource ID of the preference XML file.
399      * @param readAgain Whether to re-read the default values.
400      * If false, this method will set the default values only if this
401      * method has never been called in the past (or if the
402      * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
403      * preferences file is false). To attempt to set the default values again
404      * bypassing this check, set {@code readAgain} to true.
405      *            <p class="note">
406      *            Note: this will NOT reset preferences back to their default
407      *            values. For that functionality, use
408      *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
409      *            and clear it followed by a call to this method with this
410      *            parameter set to true.
411      *
412      * @see #setDefaultValues(Context, int, boolean)
413      * @see #setSharedPreferencesName(String)
414      * @see #setSharedPreferencesMode(int)
415      */
setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain)416     public static void setDefaultValues(Context context, String sharedPreferencesName,
417             int sharedPreferencesMode, int resId, boolean readAgain) {
418         final SharedPreferences defaultValueSp = context.getSharedPreferences(
419                 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
420 
421         if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
422             final PreferenceManager pm = new PreferenceManager(context);
423             pm.setSharedPreferencesName(sharedPreferencesName);
424             pm.setSharedPreferencesMode(sharedPreferencesMode);
425             pm.inflateFromResource(context, resId, null);
426 
427             SharedPreferences.Editor editor =
428                     defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
429 
430             SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
431         }
432     }
433 
434     /**
435      * Returns an editor to use when modifying the shared preferences.
436      * <p>
437      * Do NOT commit unless {@link #shouldCommit()} returns true.
438      *
439      * @return An editor to use to write to shared preferences.
440      * @see #shouldCommit()
441      */
getEditor()442     SharedPreferences.Editor getEditor() {
443 
444         if (mNoCommit) {
445             if (mEditor == null) {
446                 mEditor = getSharedPreferences().edit();
447             }
448 
449             return mEditor;
450         } else {
451             return getSharedPreferences().edit();
452         }
453     }
454 
455     /**
456      * Whether it is the client's responsibility to commit on the
457      * {@link #getEditor()}. This will return false in cases where the writes
458      * should be batched, for example when inflating preferences from XML.
459      *
460      * @return Whether the client should commit.
461      */
shouldCommit()462     boolean shouldCommit() {
463         return !mNoCommit;
464     }
465 
setNoCommit(boolean noCommit)466     private void setNoCommit(boolean noCommit) {
467         if (!noCommit && mEditor != null) {
468             SharedPreferencesCompat.EditorCompat.getInstance().apply(mEditor);
469         }
470         mNoCommit = noCommit;
471     }
472 
473     /**
474      * Returns the context.
475      *
476      * @return The context.
477      */
getContext()478     public Context getContext() {
479         return mContext;
480     }
481 
getOnDisplayPreferenceDialogListener()482     public OnDisplayPreferenceDialogListener getOnDisplayPreferenceDialogListener() {
483         return mOnDisplayPreferenceDialogListener;
484     }
485 
setOnDisplayPreferenceDialogListener( OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener)486     public void setOnDisplayPreferenceDialogListener(
487             OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener) {
488         mOnDisplayPreferenceDialogListener = onDisplayPreferenceDialogListener;
489     }
490 
491     /**
492      * Called when a preference requests that a dialog be shown to complete a user interaction.
493      *
494      * @param preference The preference requesting the dialog.
495      */
showDialog(Preference preference)496     public void showDialog(Preference preference) {
497         if (mOnDisplayPreferenceDialogListener != null) {
498             mOnDisplayPreferenceDialogListener.onDisplayPreferenceDialog(preference);
499         }
500     }
501 
502     /**
503      * Sets the callback to be invoked when a {@link Preference} in the
504      * hierarchy rooted at this {@link PreferenceManager} is clicked.
505      *
506      * @param listener The callback to be invoked.
507      */
setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener)508     public void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
509         mOnPreferenceTreeClickListener = listener;
510     }
511 
getOnPreferenceTreeClickListener()512     public OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
513         return mOnPreferenceTreeClickListener;
514     }
515 
516     /**
517      * Sets the callback to be invoked when a {@link PreferenceScreen} in the hierarchy rooted at
518      * this {@link PreferenceManager} is clicked.
519      *
520      * @param listener The callback to be invoked.
521      */
setOnNavigateToScreenListener(OnNavigateToScreenListener listener)522     public void setOnNavigateToScreenListener(OnNavigateToScreenListener listener) {
523         mOnNavigateToScreenListener = listener;
524     }
525 
526     /**
527      * Returns the {@link PreferenceManager.OnNavigateToScreenListener}, if one has been set.
528      */
getOnNavigateToScreenListener()529     public OnNavigateToScreenListener getOnNavigateToScreenListener() {
530         return mOnNavigateToScreenListener;
531     }
532 
533     /**
534      * Interface definition for a callback to be invoked when a
535      * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
536      * clicked.
537      */
538     public interface OnPreferenceTreeClickListener {
539         /**
540          * Called when a preference in the tree rooted at this
541          * {@link PreferenceScreen} has been clicked.
542          *
543          * @param preference The preference that was clicked.
544          * @return Whether the click was handled.
545          */
onPreferenceTreeClick(Preference preference)546         boolean onPreferenceTreeClick(Preference preference);
547     }
548 
549     /**
550      * Interface definition for a class that will be called when a
551      * {@link android.support.v7.preference.Preference} requests to display a dialog.
552      */
553     public interface OnDisplayPreferenceDialogListener {
554 
555         /**
556          * Called when a preference in the tree requests to display a dialog.
557          *
558          * @param preference The Preference object requesting the dialog.
559          */
onDisplayPreferenceDialog(Preference preference)560         void onDisplayPreferenceDialog(Preference preference);
561     }
562 
563     /**
564      * Interface definition for a class that will be called when a
565      * {@link android.support.v7.preference.PreferenceScreen} requests navigation.
566      */
567     public interface OnNavigateToScreenListener {
568 
569         /**
570          * Called when a PreferenceScreen in the tree requests to navigate to its contents.
571          *
572          * @param preferenceScreen The PreferenceScreen requesting navigation.
573          */
onNavigateToScreen(PreferenceScreen preferenceScreen)574         void onNavigateToScreen(PreferenceScreen preferenceScreen);
575     }
576 
577 }
578