blob: b84995b6b571525fa12141071902a680c77b0530 [file] [log] [blame]
package android.server.am.settings;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.Settings.SettingNotFoundException;
import androidx.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
/**
* Helper class to save, set, and restore global system-level preferences.
* <p>
* To use this class, testing APK must be self-instrumented and have
* {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}.
* <p>
* A test that changes system-level preferences can be written easily and reliably.
* <pre>
* static class PrefSession extends SettingsSession<String> {
* PrefSession() {
* super(android.provider.Settings.Secure.getUriFor(
* android.provider.Settings.Secure.PREFERENCE_KEY),
* android.provider.Settings.Secure::getString,
* android.provider.Settings.Secure::putString);
* }
* }
*
* @Test
* public void doTest() throws Exception {
* try (final PrefSession prefSession = new PrefSession()) {
* prefSession.set("value 1");
* doTest1();
* prefSession.set("value 2");
* doTest2();
* }
* }
* </pre>
*/
public class SettingsSession<T> implements AutoCloseable {
private static final String TAG = SettingsSession.class.getSimpleName();
private static final boolean DEBUG = false;
@FunctionalInterface
public interface SettingsGetter<T> {
T get(ContentResolver cr, String key) throws SettingNotFoundException;
}
@FunctionalInterface
public interface SettingsSetter<T> {
void set(ContentResolver cr, String key, T value);
}
/**
* To debug to detect nested sessions for the same key. Enabled when {@link #DEBUG} is true.
* Note that nested sessions can be merged into one session.
*/
private static final SessionCounters sSessionCounters = new SessionCounters();
protected final Uri mUri;
private final SettingsGetter<T> mGetter;
private final SettingsSetter<T> mSetter;
private final boolean mHasInitialValue;
private final T mInitialValue;
public SettingsSession(final Uri uri, final SettingsGetter<T> getter,
final SettingsSetter<T> setter) {
mUri = uri;
mGetter = getter;
mSetter = setter;
T initialValue;
boolean hasInitialValue;
try {
initialValue = get(uri, getter);
hasInitialValue = true;
} catch (SettingNotFoundException e) {
initialValue = null;
hasInitialValue = false;
}
mInitialValue = initialValue;
mHasInitialValue = hasInitialValue;
if (DEBUG) {
Log.i(TAG, "start: uri=" + uri
+ (mHasInitialValue ? " value=" + mInitialValue : " undefined"));
sSessionCounters.open(uri);
}
}
public void set(final @NonNull T value) throws Exception {
put(mUri, mSetter, value);
if (DEBUG) {
Log.i(TAG, " set: uri=" + mUri + " value=" + value);
}
}
public T get() throws SettingNotFoundException {
return get(mUri, mGetter);
}
@Override
public void close() throws Exception {
if (mHasInitialValue) {
put(mUri, mSetter, mInitialValue);
if (DEBUG) {
Log.i(TAG, "close: uri=" + mUri + " value=" + mInitialValue);
}
} else {
try {
delete(mUri);
if (DEBUG) {
Log.i(TAG, "close: uri=" + mUri + " deleted");
}
} catch (IllegalArgumentException e) {
Log.w(TAG, "Can't delete settings " + mUri, e);
}
}
if (DEBUG) {
sSessionCounters.close(mUri);
}
}
private static <T> void put(final Uri uri, final SettingsSetter<T> setter, T value)
throws SettingNotFoundException {
setter.set(getContentResolver(), uri.getLastPathSegment(), value);
}
private static <T> T get(final Uri uri, final SettingsGetter<T> getter)
throws SettingNotFoundException {
return getter.get(getContentResolver(), uri.getLastPathSegment());
}
private static void delete(final Uri uri) throws IllegalArgumentException {
getContentResolver().delete(uri, null, null);
}
private static ContentResolver getContentResolver() {
return InstrumentationRegistry.getTargetContext().getContentResolver();
}
private static class SessionCounters {
private final Map<Uri, Integer> mOpenSessions = new HashMap<>();
void open(final Uri uri) {
final Integer count = mOpenSessions.get(uri);
if (count == null) {
mOpenSessions.put(uri, 1);
return;
}
mOpenSessions.put(uri, count + 1);
Log.w(TAG, "Open nested session for " + uri, new Throwable());
}
void close(final Uri uri) {
final int count = mOpenSessions.get(uri);
if (count == 1) {
mOpenSessions.remove(uri);
return;
}
mOpenSessions.put(uri, count - 1);
Log.w(TAG, "Close nested session for " + uri, new Throwable());
}
}
}