blob: e262d38f86360a220c4c46d74c1f216d9df326d6 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.preferences;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ContextUtils;
import org.chromium.base.StrictModeContext;
import org.chromium.base.annotations.RemovableInRelease;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Layer over android {@link SharedPreferences}.
*/
@SuppressWarnings("UseSharedPreferencesManagerFromChromeCheck")
public class SharedPreferencesManager {
private static class LazyHolder {
static final SharedPreferencesManager INSTANCE = new SharedPreferencesManager();
}
/**
* @return The SharedPreferencesManager singleton.
*/
public static SharedPreferencesManager getInstance() {
return LazyHolder.INSTANCE;
}
private BaseChromePreferenceKeyChecker mKeyChecker;
private SharedPreferencesManager() {
maybeInitializeChecker();
// In production builds, use a dummy key checker.
if (mKeyChecker == null) {
mKeyChecker = new BaseChromePreferenceKeyChecker();
}
}
@VisibleForTesting
SharedPreferencesManager(BaseChromePreferenceKeyChecker keyChecker) {
mKeyChecker = keyChecker;
}
@RemovableInRelease
private void maybeInitializeChecker() {
// Create a working key checker, which does not happen in production builds.
mKeyChecker = ChromePreferenceKeyChecker.getInstance();
}
@VisibleForTesting
BaseChromePreferenceKeyChecker swapKeyCheckerForTesting(
BaseChromePreferenceKeyChecker newChecker) {
BaseChromePreferenceKeyChecker swappedOut = mKeyChecker;
mKeyChecker = newChecker;
return swappedOut;
}
/**
* Observes preference changes.
*/
public interface Observer {
/**
* Notifies when a preference maintained by {@link SharedPreferencesManager} is changed.
* @param key The key of the preference changed.
*/
void onPreferenceChanged(String key);
}
private final Map<Observer, SharedPreferences.OnSharedPreferenceChangeListener> mObservers =
new HashMap<>();
/**
* @param observer The {@link Observer} to be added for observing preference changes.
*/
public void addObserver(Observer observer) {
SharedPreferences.OnSharedPreferenceChangeListener listener =
(SharedPreferences sharedPreferences, String s) -> observer.onPreferenceChanged(s);
mObservers.put(observer, listener);
ContextUtils.getAppSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}
/**
* @param observer The {@link Observer} to be removed from observing preference changes.
*/
public void removeObserver(Observer observer) {
SharedPreferences.OnSharedPreferenceChangeListener listener = mObservers.get(observer);
if (listener == null) return;
ContextUtils.getAppSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
}
/**
* Reads set of String values from preferences.
*
* Note that you must not modify the set instance returned by this call.
*/
public Set<String> readStringSet(String key) {
return readStringSet(key, Collections.emptySet());
}
/**
* Reads set of String values from preferences.
*
* Note that you must not modify the set instance returned by this call.
*/
public Set<String> readStringSet(String key, Set<String> defaultValue) {
mKeyChecker.checkIsKeyInUse(key);
return ContextUtils.getAppSharedPreferences().getStringSet(key, defaultValue);
}
/**
* Adds a value to string set in shared preferences.
*/
public void addToStringSet(String key, String value) {
mKeyChecker.checkIsKeyInUse(key);
Set<String> values = new HashSet<>(
ContextUtils.getAppSharedPreferences().getStringSet(key, Collections.emptySet()));
values.add(value);
writeStringSetUnchecked(key, values, false);
}
/**
* Removes value from string set in shared preferences.
*/
public void removeFromStringSet(String key, String value) {
mKeyChecker.checkIsKeyInUse(key);
Set<String> values = new HashSet<>(
ContextUtils.getAppSharedPreferences().getStringSet(key, Collections.emptySet()));
if (values.remove(value)) {
writeStringSetUnchecked(key, values, false);
}
}
/**
* Writes string set to shared preferences.
*/
public void writeStringSet(String key, Set<String> values) {
mKeyChecker.checkIsKeyInUse(key);
writeStringSetUnchecked(key, values, /*sync=*/false);
}
/**
* Writes string set to shared preferences.
*/
public boolean writeStringSet(String key, Set<String> values, boolean sync) {
mKeyChecker.checkIsKeyInUse(key);
return writeStringSetUnchecked(key, values, sync);
}
/**
* Writes string set to shared preferences.
*/
private boolean writeStringSetUnchecked(String key, Set<String> values, boolean sync) {
Editor editor = ContextUtils.getAppSharedPreferences().edit().putStringSet(key, values);
if (sync) {
return editor.commit();
} else {
editor.apply();
return true;
}
}
/**
* Writes the given int value to the named shared preference.
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
public void writeInt(String key, int value) {
mKeyChecker.checkIsKeyInUse(key);
writeIntUnchecked(key, value);
}
private void writeIntUnchecked(String key, int value) {
SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
ed.putInt(key, value);
ed.apply();
}
/**
* Reads the given int value from the named shared preference, defaulting to 0 if not found.
* @param key The name of the preference to return.
* @return The value of the preference.
*/
public int readInt(String key) {
return readInt(key, 0);
}
/**
* Reads the given int value from the named shared preference.
* @param key The name of the preference to return.
* @param defaultValue The default value to return if the preference is not set.
* @return The value of the preference.
*/
public int readInt(String key, int defaultValue) {
mKeyChecker.checkIsKeyInUse(key);
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return ContextUtils.getAppSharedPreferences().getInt(key, defaultValue);
}
}
/**
* Increments the integer value specified by the given key. If no initial value is present then
* an initial value of 0 is assumed and incremented, so a new value of 1 is set.
* @param key The key specifying which integer value to increment.
* @return The newly incremented value.
*/
public int incrementInt(String key) {
mKeyChecker.checkIsKeyInUse(key);
int value = ContextUtils.getAppSharedPreferences().getInt(key, 0);
writeIntUnchecked(key, ++value);
return value;
}
/**
* Writes the given long to the named shared preference.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
public void writeLong(String key, long value) {
mKeyChecker.checkIsKeyInUse(key);
SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
ed.putLong(key, value);
ed.apply();
}
/**
* Reads the given long value from the named shared preference.
*
* @param key The name of the preference to return.
* @return The value of the preference if stored; defaultValue otherwise.
*/
public long readLong(String key) {
return readLong(key, 0);
}
/**
* Reads the given long value from the named shared preference.
*
* @param key The name of the preference to return.
* @param defaultValue The default value to return if there's no value stored.
* @return The value of the preference if stored; defaultValue otherwise.
*/
public long readLong(String key, long defaultValue) {
mKeyChecker.checkIsKeyInUse(key);
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return ContextUtils.getAppSharedPreferences().getLong(key, defaultValue);
}
}
/**
* Writes the given boolean to the named shared preference.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
public void writeBoolean(String key, boolean value) {
mKeyChecker.checkIsKeyInUse(key);
SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
ed.putBoolean(key, value);
ed.apply();
}
/**
* Reads the given boolean value from the named shared preference.
*
* @param key The name of the preference to return.
* @param defaultValue The default value to return if there's no value stored.
* @return The value of the preference if stored; defaultValue otherwise.
*/
public boolean readBoolean(String key, boolean defaultValue) {
mKeyChecker.checkIsKeyInUse(key);
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return ContextUtils.getAppSharedPreferences().getBoolean(key, defaultValue);
}
}
/**
* Writes the given string to the named shared preference.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
*/
public void writeString(String key, String value) {
mKeyChecker.checkIsKeyInUse(key);
SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
ed.putString(key, value);
ed.apply();
}
/**
* Reads the given String value from the named shared preference.
*
* @param key The name of the preference to return.
* @param defaultValue The default value to return if there's no value stored.
* @return The value of the preference if stored; defaultValue otherwise.
*/
public String readString(String key, @Nullable String defaultValue) {
mKeyChecker.checkIsKeyInUse(key);
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return ContextUtils.getAppSharedPreferences().getString(key, defaultValue);
}
}
/**
* Removes the shared preference entry.
*
* @param key The key of the preference to remove.
*/
public void removeKey(String key) {
mKeyChecker.checkIsKeyInUse(key);
SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
ed.remove(key);
ed.apply();
}
public boolean removeKey(String key, boolean sync) {
mKeyChecker.checkIsKeyInUse(key);
SharedPreferences.Editor ed = ContextUtils.getAppSharedPreferences().edit();
ed.remove(key);
if (sync) {
return ed.commit();
} else {
ed.apply();
return true;
}
}
/**
* Checks if any value was written associated to a key in shared preferences.
*
* @param key The key of the preference to check.
* @return Whether any value was written for that key.
*/
public boolean contains(String key) {
mKeyChecker.checkIsKeyInUse(key);
return ContextUtils.getAppSharedPreferences().contains(key);
}
}