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