• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.shared_preferences;
6 
7 import android.content.SharedPreferences;
8 import android.content.SharedPreferences.Editor;
9 
10 import androidx.annotation.GuardedBy;
11 import androidx.annotation.Nullable;
12 import androidx.annotation.VisibleForTesting;
13 
14 import org.jni_zero.CalledByNative;
15 import org.jni_zero.JNINamespace;
16 
17 import org.chromium.base.ContextUtils;
18 import org.chromium.base.ResettersForTesting;
19 import org.chromium.build.BuildConfig;
20 
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Set;
26 
27 /** Layer over android {@link SharedPreferences}. */
28 @JNINamespace("base::android")
29 @SuppressWarnings("UseSharedPreferencesManagerFromChromeCheck")
30 public class SharedPreferencesManager {
31     @GuardedBy("sInstances")
32     public static Map<PreferenceKeyRegistry, SharedPreferencesManager> sInstances = new HashMap<>();
33 
34     private PreferenceKeyChecker mKeyChecker;
35 
SharedPreferencesManager(PreferenceKeyRegistry registry)36     protected SharedPreferencesManager(PreferenceKeyRegistry registry) {
37         mKeyChecker =
38                 BuildConfig.ENABLE_ASSERTS
39                         ? new StrictPreferenceKeyChecker(registry)
40                         : new NoOpPreferenceKeyChecker();
41     }
42 
43     @VisibleForTesting
SharedPreferencesManager(PreferenceKeyChecker keyChecker)44     SharedPreferencesManager(PreferenceKeyChecker keyChecker) {
45         mKeyChecker = keyChecker;
46     }
47 
48     /**
49      * @param registry registry of supported and deprecated preference keys.
50      *                 Should be null when ENABLE_ASSERTS = false.
51      * @return a {@link SharedPreferencesManager} that operates on SharedPreferences keys registered
52      *         in the passed |registry|
53      */
getInstanceForRegistry( @ullable PreferenceKeyRegistry registry)54     public static SharedPreferencesManager getInstanceForRegistry(
55             @Nullable PreferenceKeyRegistry registry) {
56         SharedPreferencesManager manager;
57         synchronized (sInstances) {
58             manager = sInstances.get(registry);
59             if (manager == null) {
60                 manager = new SharedPreferencesManager(registry);
61                 sInstances.put(registry, manager);
62             }
63         }
64         return manager;
65     }
66 
disableKeyCheckerForTesting()67     public void disableKeyCheckerForTesting() {
68         PreferenceKeyChecker swappedOut = mKeyChecker;
69         mKeyChecker = new NoOpPreferenceKeyChecker();
70         ResettersForTesting.register(() -> mKeyChecker = swappedOut);
71     }
72 
73     /**
74      * Reads set of String values from preferences.
75      *
76      * If no value was set for the |key|, returns an unmodifiable empty set.
77      *
78      * @return unmodifiable Set with the values
79      */
readStringSet(String key)80     public Set<String> readStringSet(String key) {
81         return readStringSet(key, Collections.emptySet());
82     }
83 
84     /**
85      * Reads set of String values from preferences.
86      *
87      * If no value was set for the |key|, returns an unmodifiable view of |defaultValue|.
88      *
89      * @return unmodifiable Set with the values
90      */
91     @Nullable
readStringSet(String key, @Nullable Set<String> defaultValue)92     public Set<String> readStringSet(String key, @Nullable Set<String> defaultValue) {
93         mKeyChecker.checkIsKeyInUse(key);
94         Set<String> values = ContextUtils.getAppSharedPreferences().getStringSet(key, defaultValue);
95         return (values != null) ? Collections.unmodifiableSet(values) : null;
96     }
97 
98     /** Adds a value to string set in shared preferences. */
addToStringSet(String key, String value)99     public void addToStringSet(String key, String value) {
100         mKeyChecker.checkIsKeyInUse(key);
101         // Construct a new set so it can be modified safely. See crbug.com/568369.
102         Set<String> values =
103                 new HashSet<>(
104                         ContextUtils.getAppSharedPreferences()
105                                 .getStringSet(key, Collections.emptySet()));
106         values.add(value);
107         writeStringSetUnchecked(key, values);
108     }
109 
110     /** Removes value from string set in shared preferences. */
removeFromStringSet(String key, String value)111     public void removeFromStringSet(String key, String value) {
112         mKeyChecker.checkIsKeyInUse(key);
113         // Construct a new set so it can be modified safely. See crbug.com/568369.
114         Set<String> values =
115                 new HashSet<>(
116                         ContextUtils.getAppSharedPreferences()
117                                 .getStringSet(key, Collections.emptySet()));
118         if (values.remove(value)) {
119             writeStringSetUnchecked(key, values);
120         }
121     }
122 
123     /** Writes string set to shared preferences. */
writeStringSet(String key, Set<String> values)124     public void writeStringSet(String key, Set<String> values) {
125         mKeyChecker.checkIsKeyInUse(key);
126         writeStringSetUnchecked(key, values);
127     }
128 
129     /** Writes string set to shared preferences. */
writeStringSetUnchecked(String key, Set<String> values)130     private void writeStringSetUnchecked(String key, Set<String> values) {
131         Editor editor = ContextUtils.getAppSharedPreferences().edit().putStringSet(key, values);
132         editor.apply();
133     }
134 
135     /**
136      * Writes the given string set to the named shared preference and immediately commit to disk.
137      * @param key The name of the preference to modify.
138      * @param value The new value for the preference.
139      * @return Whether the operation succeeded.
140      */
writeStringSetSync(String key, Set<String> value)141     public boolean writeStringSetSync(String key, Set<String> value) {
142         mKeyChecker.checkIsKeyInUse(key);
143         Editor editor = ContextUtils.getAppSharedPreferences().edit().putStringSet(key, value);
144         return editor.commit();
145     }
146 
147     /**
148      * Writes the given int value to the named shared preference.
149      * @param key The name of the preference to modify.
150      * @param value The new value for the preference.
151      */
writeInt(String key, int value)152     public void writeInt(String key, int value) {
153         mKeyChecker.checkIsKeyInUse(key);
154         writeIntUnchecked(key, value);
155     }
156 
writeIntUnchecked(String key, int value)157     private void writeIntUnchecked(String key, int value) {
158         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
159         ed.putInt(key, value);
160         ed.apply();
161     }
162 
163     /**
164      * Writes the given int value to the named shared preference and immediately commit to disk.
165      * @param key The name of the preference to modify.
166      * @param value The new value for the preference.
167      * @return Whether the operation succeeded.
168      */
writeIntSync(String key, int value)169     public boolean writeIntSync(String key, int value) {
170         mKeyChecker.checkIsKeyInUse(key);
171         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
172         ed.putInt(key, value);
173         return ed.commit();
174     }
175 
176     /**
177      * Reads the given int value from the named shared preference, defaulting to 0 if not found.
178      * @param key The name of the preference to return.
179      * @return The value of the preference.
180      */
readInt(String key)181     public int readInt(String key) {
182         return readInt(key, 0);
183     }
184 
185     /**
186      * Reads the given int value from the named shared preference.
187      * @param key The name of the preference to return.
188      * @param defaultValue The default value to return if the preference is not set.
189      * @return The value of the preference.
190      */
191     @CalledByNative
readInt(String key, int defaultValue)192     public int readInt(String key, int defaultValue) {
193         mKeyChecker.checkIsKeyInUse(key);
194         return ContextUtils.getAppSharedPreferences().getInt(key, defaultValue);
195     }
196 
197     /**
198      * Reads all int values associated with keys with the given prefix.
199      *
200      * @param prefix The key prefix for which all values should be returned.
201      * @return Map from the keys (in full, not just stem) to Integer values.
202      */
readIntsWithPrefix(KeyPrefix prefix)203     public Map<String, Integer> readIntsWithPrefix(KeyPrefix prefix) {
204         return readAllWithPrefix(prefix);
205     }
206 
207     /**
208      * Increments the integer value specified by the given key.  If no initial value is present then
209      * an initial value of 0 is assumed and incremented, so a new value of 1 is set.
210      * @param key The key specifying which integer value to increment.
211      * @return The newly incremented value.
212      */
incrementInt(String key)213     public int incrementInt(String key) {
214         int value = readInt(key, 0);
215         writeIntUnchecked(key, ++value);
216         return value;
217     }
218 
219     /**
220      * Writes the given long to the named shared preference.
221      *
222      * @param key The name of the preference to modify.
223      * @param value The new value for the preference.
224      */
writeLong(String key, long value)225     public void writeLong(String key, long value) {
226         mKeyChecker.checkIsKeyInUse(key);
227         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
228         ed.putLong(key, value);
229         ed.apply();
230     }
231 
232     /**
233      * Writes the given long value to the named shared preference and immediately commit to disk.
234      * @param key The name of the preference to modify.
235      * @param value The new value for the preference.
236      * @return Whether the operation succeeded.
237      */
writeLongSync(String key, long value)238     public boolean writeLongSync(String key, long value) {
239         mKeyChecker.checkIsKeyInUse(key);
240         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
241         ed.putLong(key, value);
242         return ed.commit();
243     }
244 
245     /**
246      * Reads the given long value from the named shared preference.
247      *
248      * @param key The name of the preference to return.
249      * @return The value of the preference if stored; defaultValue otherwise.
250      */
readLong(String key)251     public long readLong(String key) {
252         return readLong(key, 0);
253     }
254 
255     /**
256      * Reads the given long value from the named shared preference.
257      *
258      * @param key The name of the preference to return.
259      * @param defaultValue The default value to return if there's no value stored.
260      * @return The value of the preference if stored; defaultValue otherwise.
261      */
readLong(String key, long defaultValue)262     public long readLong(String key, long defaultValue) {
263         mKeyChecker.checkIsKeyInUse(key);
264         return ContextUtils.getAppSharedPreferences().getLong(key, defaultValue);
265     }
266 
267     /**
268      * Reads all long values associated with keys with the given prefix.
269      *
270      * @param prefix The key prefix for which all values should be returned.
271      * @return Map from the keys (in full, not just stem) to Long values.
272      */
readLongsWithPrefix(KeyPrefix prefix)273     public Map<String, Long> readLongsWithPrefix(KeyPrefix prefix) {
274         return readAllWithPrefix(prefix);
275     }
276 
277     /**
278      * Writes the given float to the named shared preference.
279      *
280      * @param key The name of the preference to modify.
281      * @param value The new value for the preference.
282      */
writeFloat(String key, float value)283     public void writeFloat(String key, float value) {
284         mKeyChecker.checkIsKeyInUse(key);
285         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
286         ed.putFloat(key, value);
287         ed.apply();
288     }
289 
290     /**
291      * Writes the given float value to the named shared preference and immediately commit to disk.
292      *
293      * @param key The name of the preference to modify.
294      * @param value The new value for the preference.
295      * @return Whether the operation succeeded.
296      */
writeFloatSync(String key, float value)297     public boolean writeFloatSync(String key, float value) {
298         mKeyChecker.checkIsKeyInUse(key);
299         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
300         ed.putFloat(key, value);
301         return ed.commit();
302     }
303 
304     /**
305      * Reads the given float value from the named shared preference.
306      *
307      * @param key The name of the preference to return.
308      * @param defaultValue The default value to return if there's no value stored.
309      * @return The value of the preference if stored; defaultValue otherwise.
310      */
readFloat(String key, float defaultValue)311     public float readFloat(String key, float defaultValue) {
312         mKeyChecker.checkIsKeyInUse(key);
313         return ContextUtils.getAppSharedPreferences().getFloat(key, defaultValue);
314     }
315 
316     /**
317      * Reads all float values associated with keys with the given prefix.
318      *
319      * @param prefix The key prefix for which all values should be returned.
320      * @return Map from the keys (in full, not just stem) to Float values.
321      */
readFloatsWithPrefix(KeyPrefix prefix)322     public Map<String, Float> readFloatsWithPrefix(KeyPrefix prefix) {
323         return readAllWithPrefix(prefix);
324     }
325 
326     /**
327      * Writes the given double value to the named shared preference.
328      *
329      * @param key The name of the preference to modify.
330      * @param value The new value for the preference.
331      */
writeDouble(String key, double value)332     public void writeDouble(String key, double value) {
333         mKeyChecker.checkIsKeyInUse(key);
334         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
335         long ieee754LongValue = Double.doubleToRawLongBits(value);
336         ed.putLong(key, ieee754LongValue);
337         ed.apply();
338     }
339 
340     /**
341      * Reads the given double value from the named shared preference.
342      *
343      * @param key The name of the preference to return.
344      * @param defaultValue The default value to return if there's no value stored.
345      * @return The value of the preference if stored; defaultValue otherwise.
346      */
readDouble(String key, double defaultValue)347     public Double readDouble(String key, double defaultValue) {
348         mKeyChecker.checkIsKeyInUse(key);
349         SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
350         if (!prefs.contains(key)) {
351             return defaultValue;
352         }
353         long ieee754LongValue = prefs.getLong(key, 0L);
354         return Double.longBitsToDouble(ieee754LongValue);
355     }
356 
357     /**
358      * Reads all double values associated with keys with the given prefix.
359      *
360      * @param prefix The key prefix for which all values should be returned.
361      * @return Map from the keys (in full, not just stem) to Double values.
362      */
readDoublesWithPrefix(KeyPrefix prefix)363     public Map<String, Double> readDoublesWithPrefix(KeyPrefix prefix) {
364         Map<String, Long> longMap = readLongsWithPrefix(prefix);
365         Map<String, Double> doubleMap = new HashMap<>();
366 
367         for (Map.Entry<String, Long> longEntry : longMap.entrySet()) {
368             long ieee754LongValue = longEntry.getValue();
369             double doubleValue = Double.longBitsToDouble(ieee754LongValue);
370             doubleMap.put(longEntry.getKey(), doubleValue);
371         }
372         return doubleMap;
373     }
374 
375     /**
376      * Writes the given boolean to the named shared preference.
377      *
378      * @param key The name of the preference to modify.
379      * @param value The new value for the preference.
380      */
writeBoolean(String key, boolean value)381     public void writeBoolean(String key, boolean value) {
382         mKeyChecker.checkIsKeyInUse(key);
383         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
384         ed.putBoolean(key, value);
385         ed.apply();
386     }
387 
388     /**
389      * Writes the given boolean value to the named shared preference and immediately commit to disk.
390      * @param key The name of the preference to modify.
391      * @param value The new value for the preference.
392      * @return Whether the operation succeeded.
393      */
writeBooleanSync(String key, boolean value)394     public boolean writeBooleanSync(String key, boolean value) {
395         mKeyChecker.checkIsKeyInUse(key);
396         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
397         ed.putBoolean(key, value);
398         return ed.commit();
399     }
400 
401     /**
402      * Reads the given boolean value from the named shared preference.
403      *
404      * @param key The name of the preference to return.
405      * @param defaultValue The default value to return if there's no value stored.
406      * @return The value of the preference if stored; defaultValue otherwise.
407      */
408     @CalledByNative
readBoolean(String key, boolean defaultValue)409     public boolean readBoolean(String key, boolean defaultValue) {
410         mKeyChecker.checkIsKeyInUse(key);
411         return ContextUtils.getAppSharedPreferences().getBoolean(key, defaultValue);
412     }
413 
414     /**
415      * Reads all boolean values associated with keys with the given prefix.
416      *
417      * @param prefix The key prefix for which all values should be returned.
418      * @return Map from the keys (in full, not just stem) to Boolean values.
419      */
readBooleansWithPrefix(KeyPrefix prefix)420     public Map<String, Boolean> readBooleansWithPrefix(KeyPrefix prefix) {
421         return readAllWithPrefix(prefix);
422     }
423 
424     /**
425      * Writes the given string to the named shared preference.
426      *
427      * @param key The name of the preference to modify.
428      * @param value The new value for the preference.
429      */
430     @CalledByNative
writeString(String key, String value)431     public void writeString(String key, String value) {
432         mKeyChecker.checkIsKeyInUse(key);
433         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
434         ed.putString(key, value);
435         ed.apply();
436     }
437 
438     /**
439      * Writes the given string value to the named shared preference and immediately commit to disk.
440      * @param key The name of the preference to modify.
441      * @param value The new value for the preference.
442      * @return Whether the operation succeeded.
443      */
writeStringSync(String key, String value)444     public boolean writeStringSync(String key, String value) {
445         mKeyChecker.checkIsKeyInUse(key);
446         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
447         ed.putString(key, value);
448         return ed.commit();
449     }
450 
451     /**
452      * Reads the given String value from the named shared preference.
453      *
454      * @param key The name of the preference to return.
455      * @param defaultValue The default value to return if there's no value stored.
456      * @return The value of the preference if stored; defaultValue otherwise.
457      */
458     @CalledByNative
459     @Nullable
readString(String key, @Nullable String defaultValue)460     public String readString(String key, @Nullable String defaultValue) {
461         mKeyChecker.checkIsKeyInUse(key);
462         return ContextUtils.getAppSharedPreferences().getString(key, defaultValue);
463     }
464 
465     /**
466      * Reads all String values associated with keys with the given prefix.
467      *
468      * @param prefix The key prefix for which all values should be returned.
469      * @return Map from the keys (in full, not just stem) to String values.
470      */
readStringsWithPrefix(KeyPrefix prefix)471     public Map<String, String> readStringsWithPrefix(KeyPrefix prefix) {
472         return readAllWithPrefix(prefix);
473     }
474 
475     /**
476      * Removes the shared preference entry.
477      *
478      * @param key The key of the preference to remove.
479      */
480     @CalledByNative
removeKey(String key)481     public void removeKey(String key) {
482         mKeyChecker.checkIsKeyInUse(key);
483         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
484         ed.remove(key);
485         ed.apply();
486     }
487 
removeKeySync(String key)488     public boolean removeKeySync(String key) {
489         mKeyChecker.checkIsKeyInUse(key);
490         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
491         ed.remove(key);
492         return ed.commit();
493     }
494 
495     /**
496      * Removes all shared preference entries with the given prefix.
497      *
498      * @param prefix The KeyPrefix for which all entries should be removed.
499      */
removeKeysWithPrefix(KeyPrefix prefix)500     public void removeKeysWithPrefix(KeyPrefix prefix) {
501         mKeyChecker.checkIsPrefixInUse(prefix);
502         SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
503         Map<String, ?> allPrefs = ContextUtils.getAppSharedPreferences().getAll();
504         for (Map.Entry<String, ?> pref : allPrefs.entrySet()) {
505             String key = pref.getKey();
506             if (prefix.hasGenerated(key)) {
507                 ed.remove(key);
508             }
509         }
510         ed.apply();
511     }
512 
513     /**
514      * Checks if any value was written associated to a key in shared preferences.
515      *
516      * @param key The key of the preference to check.
517      * @return Whether any value was written for that key.
518      */
519     @CalledByNative
contains(String key)520     public boolean contains(String key) {
521         mKeyChecker.checkIsKeyInUse(key);
522         return ContextUtils.getAppSharedPreferences().contains(key);
523     }
524 
readAllWithPrefix(KeyPrefix prefix)525     private <T> Map<String, T> readAllWithPrefix(KeyPrefix prefix) {
526         mKeyChecker.checkIsPrefixInUse(prefix);
527         Map<String, ?> allPrefs = ContextUtils.getAppSharedPreferences().getAll();
528         Map<String, T> allPrefsWithPrefix = new HashMap<>();
529         for (Map.Entry<String, ?> pref : allPrefs.entrySet()) {
530             String key = pref.getKey();
531             if (prefix.hasGenerated(key)) {
532                 allPrefsWithPrefix.put(key, (T) pref.getValue());
533             }
534         }
535         return allPrefsWithPrefix;
536     }
537 }
538