Revert "SettingsProvider enhanced cache"
This reverts commit 6c5d9a1aa4.
Reason for revert: testing for b/270623927
Change-Id: I625ff474ca1923ba09a20ceb7669f8e371c22026
This commit is contained in:
committed by
Android (Google) Code Review
parent
6c5d9a1aa4
commit
b8d8b9332e
@@ -116,7 +116,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The Settings provider contains global system-level device preferences.
|
||||
@@ -2979,22 +2978,19 @@ public final class Settings {
|
||||
}
|
||||
|
||||
private static final class GenerationTracker {
|
||||
@NonNull private final String mName;
|
||||
@NonNull private final MemoryIntArray mArray;
|
||||
@NonNull private final Consumer<String> mErrorHandler;
|
||||
private final MemoryIntArray mArray;
|
||||
private final Runnable mErrorHandler;
|
||||
private final int mIndex;
|
||||
private int mCurrentGeneration;
|
||||
|
||||
GenerationTracker(@NonNull String name, @NonNull MemoryIntArray array, int index,
|
||||
int generation, Consumer<String> errorHandler) {
|
||||
mName = name;
|
||||
public GenerationTracker(@NonNull MemoryIntArray array, int index,
|
||||
int generation, Runnable errorHandler) {
|
||||
mArray = array;
|
||||
mIndex = index;
|
||||
mErrorHandler = errorHandler;
|
||||
mCurrentGeneration = generation;
|
||||
}
|
||||
|
||||
// This method also updates the obsolete generation code stored locally
|
||||
public boolean isGenerationChanged() {
|
||||
final int currentGeneration = readCurrentGeneration();
|
||||
if (currentGeneration >= 0) {
|
||||
@@ -3015,7 +3011,9 @@ public final class Settings {
|
||||
return mArray.get(mIndex);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting current generation", e);
|
||||
mErrorHandler.accept(mName);
|
||||
if (mErrorHandler != null) {
|
||||
mErrorHandler.run();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -3025,6 +3023,9 @@ public final class Settings {
|
||||
mArray.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error closing backing array", e);
|
||||
if (mErrorHandler != null) {
|
||||
mErrorHandler.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3087,21 +3088,8 @@ public final class Settings {
|
||||
private final ArraySet<String> mAllFields;
|
||||
private final ArrayMap<String, Integer> mReadableFieldsWithMaxTargetSdk;
|
||||
|
||||
// Mapping from the name of a setting (or the prefix of a namespace) to a generation tracker
|
||||
@GuardedBy("this")
|
||||
private ArrayMap<String, GenerationTracker> mGenerationTrackers = new ArrayMap<>();
|
||||
|
||||
private Consumer<String> mGenerationTrackerErrorHandler = (String name) -> {
|
||||
synchronized (NameValueCache.this) {
|
||||
Log.e(TAG, "Error accessing generation tracker - removing");
|
||||
final GenerationTracker tracker = mGenerationTrackers.get(name);
|
||||
if (tracker != null) {
|
||||
tracker.destroy();
|
||||
mGenerationTrackers.remove(name);
|
||||
}
|
||||
mValues.remove(name);
|
||||
}
|
||||
};
|
||||
private GenerationTracker mGenerationTracker;
|
||||
|
||||
<T extends NameValueTable> NameValueCache(Uri uri, String getCommand,
|
||||
String setCommand, String deleteCommand, ContentProviderHolder providerHolder,
|
||||
@@ -3190,43 +3178,6 @@ public final class Settings {
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
|
||||
final boolean isSelf = (userHandle == UserHandle.myUserId());
|
||||
int currentGeneration = -1;
|
||||
boolean needsGenerationTracker = false;
|
||||
|
||||
if (isSelf) {
|
||||
synchronized (NameValueCache.this) {
|
||||
final GenerationTracker generationTracker = mGenerationTrackers.get(name);
|
||||
if (generationTracker != null) {
|
||||
if (generationTracker.isGenerationChanged()) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Generation changed for setting:" + name
|
||||
+ " type:" + mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName()
|
||||
+ " and user:" + userHandle);
|
||||
}
|
||||
mValues.remove(name);
|
||||
} else if (mValues.containsKey(name)) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Cache hit for setting:" + name);
|
||||
}
|
||||
return mValues.get(name);
|
||||
}
|
||||
currentGeneration = generationTracker.getCurrentGeneration();
|
||||
} else {
|
||||
needsGenerationTracker = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "get setting for user " + userHandle
|
||||
+ " by user " + UserHandle.myUserId() + " so skipping cache");
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Cache miss for setting:" + name);
|
||||
}
|
||||
|
||||
// Check if the target settings key is readable. Reject if the caller is not system and
|
||||
// is trying to access a settings key defined in the Settings.Secure, Settings.System or
|
||||
// Settings.Global and is not annotated as @Readable.
|
||||
@@ -3260,6 +3211,31 @@ public final class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
final boolean isSelf = (userHandle == UserHandle.myUserId());
|
||||
int currentGeneration = -1;
|
||||
if (isSelf) {
|
||||
synchronized (NameValueCache.this) {
|
||||
if (mGenerationTracker != null) {
|
||||
if (mGenerationTracker.isGenerationChanged()) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Generation changed for type:"
|
||||
+ mUri.getPath() + " in package:"
|
||||
+ cr.getPackageName() +" and user:" + userHandle);
|
||||
}
|
||||
mValues.clear();
|
||||
} else if (mValues.containsKey(name)) {
|
||||
return mValues.get(name);
|
||||
}
|
||||
if (mGenerationTracker != null) {
|
||||
currentGeneration = mGenerationTracker.getCurrentGeneration();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
|
||||
+ " by user " + UserHandle.myUserId() + " so skipping cache");
|
||||
}
|
||||
|
||||
IContentProvider cp = mProviderHolder.getProvider(cr);
|
||||
|
||||
// Try the fast path first, not using query(). If this
|
||||
@@ -3268,17 +3244,24 @@ public final class Settings {
|
||||
// interface.
|
||||
if (mCallGetCommand != null) {
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
Bundle args = null;
|
||||
if (!isSelf) {
|
||||
args = new Bundle();
|
||||
args.putInt(CALL_METHOD_USER_KEY, userHandle);
|
||||
}
|
||||
if (needsGenerationTracker) {
|
||||
args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Requested generation tracker for setting:" + name
|
||||
+ " type:" + mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName()
|
||||
+ " and user:" + userHandle);
|
||||
boolean needsGenerationTracker = false;
|
||||
synchronized (NameValueCache.this) {
|
||||
if (isSelf && mGenerationTracker == null) {
|
||||
needsGenerationTracker = true;
|
||||
if (args == null) {
|
||||
args = new Bundle();
|
||||
}
|
||||
args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Requested generation tracker for type: "+ mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName() +" and user:"
|
||||
+ userHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
Bundle b;
|
||||
@@ -3315,24 +3298,33 @@ public final class Settings {
|
||||
final int generation = b.getInt(
|
||||
CALL_METHOD_GENERATION_KEY, 0);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Received generation tracker for setting:"
|
||||
+ name
|
||||
+ " type:" + mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName()
|
||||
+ " and user:" + userHandle
|
||||
+ " with index:" + index);
|
||||
Log.i(TAG, "Received generation tracker for type:"
|
||||
+ mUri.getPath() + " in package:"
|
||||
+ cr.getPackageName() + " and user:"
|
||||
+ userHandle + " with index:" + index);
|
||||
}
|
||||
mGenerationTrackers.put(name, new GenerationTracker(name,
|
||||
array, index, generation,
|
||||
mGenerationTrackerErrorHandler));
|
||||
if (mGenerationTracker != null) {
|
||||
mGenerationTracker.destroy();
|
||||
}
|
||||
mGenerationTracker = new GenerationTracker(array, index,
|
||||
generation, () -> {
|
||||
synchronized (NameValueCache.this) {
|
||||
Log.e(TAG, "Error accessing generation"
|
||||
+ " tracker - removing");
|
||||
if (mGenerationTracker != null) {
|
||||
GenerationTracker generationTracker =
|
||||
mGenerationTracker;
|
||||
mGenerationTracker = null;
|
||||
generationTracker.destroy();
|
||||
mValues.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
currentGeneration = generation;
|
||||
}
|
||||
}
|
||||
if (mGenerationTrackers.get(name) != null && currentGeneration
|
||||
== mGenerationTrackers.get(name).getCurrentGeneration()) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Updating cache for setting:" + name);
|
||||
}
|
||||
if (mGenerationTracker != null && currentGeneration ==
|
||||
mGenerationTracker.getCurrentGeneration()) {
|
||||
mValues.put(name, value);
|
||||
}
|
||||
}
|
||||
@@ -3375,14 +3367,15 @@ public final class Settings {
|
||||
|
||||
String value = c.moveToNext() ? c.getString(0) : null;
|
||||
synchronized (NameValueCache.this) {
|
||||
if (mGenerationTrackers.get(name) != null && currentGeneration
|
||||
== mGenerationTrackers.get(name).getCurrentGeneration()) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Updating cache for setting:" + name + " using query");
|
||||
}
|
||||
if (mGenerationTracker != null
|
||||
&& currentGeneration == mGenerationTracker.getCurrentGeneration()) {
|
||||
mValues.put(name, value);
|
||||
}
|
||||
}
|
||||
if (LOCAL_LOGV) {
|
||||
Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
|
||||
name + " = " + (value == null ? "(null)" : value));
|
||||
}
|
||||
return value;
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
|
||||
@@ -3416,29 +3409,18 @@ public final class Settings {
|
||||
Config.enforceReadPermission(namespace);
|
||||
ArrayMap<String, String> keyValues = new ArrayMap<>();
|
||||
int currentGeneration = -1;
|
||||
boolean needsGenerationTracker = false;
|
||||
|
||||
synchronized (NameValueCache.this) {
|
||||
final GenerationTracker generationTracker = mGenerationTrackers.get(prefix);
|
||||
if (generationTracker != null) {
|
||||
if (generationTracker.isGenerationChanged()) {
|
||||
if (mGenerationTracker != null) {
|
||||
if (mGenerationTracker.isGenerationChanged()) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Generation changed for prefix:" + prefix
|
||||
+ " type:" + mUri.getPath()
|
||||
Log.i(TAG, "Generation changed for type:" + mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName());
|
||||
}
|
||||
for (int i = 0; i < mValues.size(); ++i) {
|
||||
String key = mValues.keyAt(i);
|
||||
if (key.startsWith(prefix)) {
|
||||
mValues.remove(key);
|
||||
}
|
||||
}
|
||||
mValues.clear();
|
||||
} else {
|
||||
boolean prefixCached = mValues.containsKey(prefix);
|
||||
if (prefixCached) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Cache hit for prefix:" + prefix);
|
||||
}
|
||||
if (!names.isEmpty()) {
|
||||
for (String name : names) {
|
||||
if (mValues.containsKey(name)) {
|
||||
@@ -3458,9 +3440,9 @@ public final class Settings {
|
||||
return keyValues;
|
||||
}
|
||||
}
|
||||
currentGeneration = generationTracker.getCurrentGeneration();
|
||||
} else {
|
||||
needsGenerationTracker = true;
|
||||
if (mGenerationTracker != null) {
|
||||
currentGeneration = mGenerationTracker.getCurrentGeneration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3468,20 +3450,20 @@ public final class Settings {
|
||||
// No list command specified, return empty map
|
||||
return keyValues;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Cache miss for prefix:" + prefix);
|
||||
}
|
||||
IContentProvider cp = mProviderHolder.getProvider(cr);
|
||||
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
|
||||
if (needsGenerationTracker) {
|
||||
args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Requested generation tracker for prefix:" + prefix
|
||||
+ " type: " + mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName());
|
||||
boolean needsGenerationTracker = false;
|
||||
synchronized (NameValueCache.this) {
|
||||
if (mGenerationTracker == null) {
|
||||
needsGenerationTracker = true;
|
||||
args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Requested generation tracker for type: "
|
||||
+ mUri.getPath() + " in package:" + cr.getPackageName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3534,22 +3516,32 @@ public final class Settings {
|
||||
final int generation = b.getInt(
|
||||
CALL_METHOD_GENERATION_KEY, 0);
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Received generation tracker for prefix:" + prefix
|
||||
+ " type:" + mUri.getPath()
|
||||
+ " in package:" + cr.getPackageName()
|
||||
+ " with index:" + index);
|
||||
Log.i(TAG, "Received generation tracker for type:"
|
||||
+ mUri.getPath() + " in package:"
|
||||
+ cr.getPackageName() + " with index:" + index);
|
||||
}
|
||||
mGenerationTrackers.put(prefix,
|
||||
new GenerationTracker(prefix, array, index, generation,
|
||||
mGenerationTrackerErrorHandler));
|
||||
if (mGenerationTracker != null) {
|
||||
mGenerationTracker.destroy();
|
||||
}
|
||||
mGenerationTracker = new GenerationTracker(array, index,
|
||||
generation, () -> {
|
||||
synchronized (NameValueCache.this) {
|
||||
Log.e(TAG, "Error accessing generation tracker"
|
||||
+ " - removing");
|
||||
if (mGenerationTracker != null) {
|
||||
GenerationTracker generationTracker =
|
||||
mGenerationTracker;
|
||||
mGenerationTracker = null;
|
||||
generationTracker.destroy();
|
||||
mValues.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
currentGeneration = generation;
|
||||
}
|
||||
}
|
||||
if (mGenerationTrackers.get(prefix) != null && currentGeneration
|
||||
== mGenerationTrackers.get(prefix).getCurrentGeneration()) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Updating cache for prefix:" + prefix);
|
||||
}
|
||||
if (mGenerationTracker != null && currentGeneration
|
||||
== mGenerationTracker.getCurrentGeneration()) {
|
||||
// cache the complete list of flags for the namespace
|
||||
mValues.putAll(flagsToValues);
|
||||
// Adding the prefix as a signal that the prefix is cached.
|
||||
@@ -3565,11 +3557,11 @@ public final class Settings {
|
||||
|
||||
public void clearGenerationTrackerForTest() {
|
||||
synchronized (NameValueCache.this) {
|
||||
for (int i = 0; i < mGenerationTrackers.size(); i++) {
|
||||
mGenerationTrackers.valueAt(i).destroy();
|
||||
if (mGenerationTracker != null) {
|
||||
mGenerationTracker.destroy();
|
||||
}
|
||||
mGenerationTrackers.clear();
|
||||
mValues.clear();
|
||||
mGenerationTracker = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,18 +32,16 @@ import android.platform.test.annotations.Presubmit;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.MemoryIntArray;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -61,63 +59,42 @@ public class NameValueCacheTest {
|
||||
private static final String NAMESPACE = "namespace";
|
||||
private static final String NAMESPACE2 = "namespace2";
|
||||
|
||||
private static final String SETTING = "test_setting";
|
||||
private static final String SETTING2 = "test_setting2";
|
||||
|
||||
@Mock
|
||||
private IContentProvider mMockIContentProvider;
|
||||
@Mock
|
||||
private ContentProvider mMockContentProvider;
|
||||
private MockContentResolver mMockContentResolver;
|
||||
private MemoryIntArray mConfigsCacheGenerationStore;
|
||||
private MemoryIntArray mSettingsCacheGenerationStore;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> mConfigsStorage;
|
||||
private HashMap<String, String> mSettingsStorage;
|
||||
private MemoryIntArray mCacheGenerationStore;
|
||||
private int mCurrentGeneration = 123;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> mStorage;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Settings.Config.clearProviderForTest();
|
||||
Settings.Secure.clearProviderForTest();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
|
||||
mMockContentResolver = new MockContentResolver(
|
||||
InstrumentationRegistry.getInstrumentation().getContext());
|
||||
mMockContentResolver = new MockContentResolver(InstrumentationRegistry
|
||||
.getInstrumentation().getContext());
|
||||
mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
|
||||
mMockContentProvider);
|
||||
mMockContentResolver.addProvider(Settings.Secure.CONTENT_URI.getAuthority(),
|
||||
mMockContentProvider);
|
||||
mConfigsCacheGenerationStore = new MemoryIntArray(2);
|
||||
mConfigsCacheGenerationStore.set(0, 123);
|
||||
mConfigsCacheGenerationStore.set(1, 456);
|
||||
mSettingsCacheGenerationStore = new MemoryIntArray(2);
|
||||
mSettingsCacheGenerationStore.set(0, 234);
|
||||
mSettingsCacheGenerationStore.set(1, 567);
|
||||
mConfigsStorage = new HashMap<>();
|
||||
mSettingsStorage = new HashMap<>();
|
||||
mCacheGenerationStore = new MemoryIntArray(1);
|
||||
mStorage = new HashMap<>();
|
||||
|
||||
// Stores keyValues for a given prefix and increments the generation. (Note that this
|
||||
// increments the generation no matter what, it doesn't pay attention to if anything
|
||||
// actually changed).
|
||||
when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class))).thenAnswer(
|
||||
invocationOnMock -> {
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
|
||||
Bundle incomingBundle = invocationOnMock.getArgument(4);
|
||||
HashMap<String, String> keyValues =
|
||||
(HashMap<String, String>) incomingBundle.getSerializable(
|
||||
Settings.CALL_METHOD_FLAGS_KEY, HashMap.class);
|
||||
Settings.CALL_METHOD_FLAGS_KEY);
|
||||
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
|
||||
mConfigsStorage.put(prefix, keyValues);
|
||||
int currentGeneration;
|
||||
// Different prefixes have different generation codes
|
||||
if (prefix.equals(NAMESPACE + "/")) {
|
||||
currentGeneration = mConfigsCacheGenerationStore.get(0);
|
||||
mConfigsCacheGenerationStore.set(0, ++currentGeneration);
|
||||
} else if (prefix.equals(NAMESPACE2 + "/")) {
|
||||
currentGeneration = mConfigsCacheGenerationStore.get(1);
|
||||
mConfigsCacheGenerationStore.set(1, ++currentGeneration);
|
||||
}
|
||||
mStorage.put(prefix, keyValues);
|
||||
mCacheGenerationStore.set(0, ++mCurrentGeneration);
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
|
||||
Settings.SET_ALL_RESULT_SUCCESS);
|
||||
@@ -125,87 +102,32 @@ public class NameValueCacheTest {
|
||||
});
|
||||
|
||||
// Returns the keyValues corresponding to a namespace, or an empty map if the namespace
|
||||
// doesn't have anything stored for it. Returns the generation key if the map isn't empty
|
||||
// and the caller asked for the generation key.
|
||||
// doesn't have anything stored for it. Returns the generation key if the caller asked
|
||||
// for one.
|
||||
when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class))).thenAnswer(
|
||||
invocationOnMock -> {
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
|
||||
Bundle incomingBundle = invocationOnMock.getArgument(4);
|
||||
|
||||
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
|
||||
|
||||
if (!mConfigsStorage.containsKey(prefix)) {
|
||||
mConfigsStorage.put(prefix, new HashMap<>());
|
||||
if (!mStorage.containsKey(prefix)) {
|
||||
mStorage.put(prefix, new HashMap<>());
|
||||
}
|
||||
HashMap<String, String> keyValues = mConfigsStorage.get(prefix);
|
||||
HashMap<String, String> keyValues = mStorage.get(prefix);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
|
||||
|
||||
if (!keyValues.isEmpty() && incomingBundle.containsKey(
|
||||
Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
|
||||
int index = prefix.equals(NAMESPACE + "/") ? 0 : 1;
|
||||
if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
|
||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
|
||||
mConfigsCacheGenerationStore);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
|
||||
mCacheGenerationStore);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
|
||||
mConfigsCacheGenerationStore.get(index));
|
||||
mCacheGenerationStore.get(0));
|
||||
}
|
||||
return bundle;
|
||||
});
|
||||
|
||||
// Stores value for a given setting's name and increments the generation. (Note that this
|
||||
// increments the generation no matter what, it doesn't pay attention to if anything
|
||||
// actually changed).
|
||||
when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
|
||||
eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class))).thenAnswer(
|
||||
invocationOnMock -> {
|
||||
Bundle incomingBundle = invocationOnMock.getArgument(4);
|
||||
String key = invocationOnMock.getArgument(3);
|
||||
String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
|
||||
mSettingsStorage.put(key, value);
|
||||
int currentGeneration;
|
||||
// Different settings have different generation codes
|
||||
if (key.equals(SETTING)) {
|
||||
currentGeneration = mSettingsCacheGenerationStore.get(0);
|
||||
mSettingsCacheGenerationStore.set(0, ++currentGeneration);
|
||||
} else if (key.equals(SETTING2)) {
|
||||
currentGeneration = mSettingsCacheGenerationStore.get(1);
|
||||
mSettingsCacheGenerationStore.set(1, ++currentGeneration);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// Returns the value corresponding to a setting, or null if the setting
|
||||
// doesn't have a value stored for it. Returns the generation key if the value isn't null
|
||||
// and the caller asked for the generation key.
|
||||
when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
|
||||
invocationOnMock -> {
|
||||
Bundle incomingBundle = invocationOnMock.getArgument(4);
|
||||
String key = invocationOnMock.getArgument(3);
|
||||
String value = mSettingsStorage.get(key);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putSerializable(Settings.NameValueTable.VALUE, value);
|
||||
|
||||
if (value != null && incomingBundle.containsKey(
|
||||
Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
|
||||
int index = key.equals(SETTING) ? 0 : 1;
|
||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
|
||||
mSettingsCacheGenerationStore);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
|
||||
mSettingsCacheGenerationStore.get(index));
|
||||
}
|
||||
return bundle;
|
||||
});
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() throws IOException {
|
||||
mConfigsStorage.clear();
|
||||
mSettingsStorage.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -213,13 +135,16 @@ public class NameValueCacheTest {
|
||||
HashMap<String, String> keyValues = new HashMap<>();
|
||||
keyValues.put("a", "b");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
|
||||
verify(mMockIContentProvider).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
|
||||
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
|
||||
NAMESPACE,
|
||||
Collections.emptyList());
|
||||
verify(mMockIContentProvider).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
@@ -231,12 +156,14 @@ public class NameValueCacheTest {
|
||||
keyValues.put("a", "c");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
|
||||
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
|
||||
@@ -250,31 +177,36 @@ public class NameValueCacheTest {
|
||||
HashMap<String, String> keyValues = new HashMap<>();
|
||||
keyValues.put("a", "b");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
|
||||
verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
verify(mMockIContentProvider).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
|
||||
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
verify(mMockIContentProvider).call(any(), any(), eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
HashMap<String, String> keyValues2 = new HashMap<>();
|
||||
keyValues2.put("c", "d");
|
||||
keyValues2.put("e", "f");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2);
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG), any(), any(Bundle.class));
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
|
||||
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE,
|
||||
Collections.emptyList());
|
||||
verify(mMockIContentProvider).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE2, Collections.emptyList());
|
||||
NAMESPACE2,
|
||||
Collections.emptyList());
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2);
|
||||
|
||||
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
// Modifying the second namespace doesn't affect the cache of the first namespace
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
@@ -287,90 +219,17 @@ public class NameValueCacheTest {
|
||||
@Test
|
||||
public void testCaching_emptyNamespace() throws Exception {
|
||||
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
|
||||
NAMESPACE,
|
||||
Collections.emptyList());
|
||||
verify(mMockIContentProvider).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues).isEmpty();
|
||||
|
||||
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
// Empty list won't be cached
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG), any(), any(Bundle.class));
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching_singleSetting() throws Exception {
|
||||
Settings.Secure.putString(mMockContentResolver, SETTING, "a");
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
|
||||
|
||||
String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
|
||||
assertThat(returnedValue).isEqualTo("a");
|
||||
|
||||
String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedValue).isEqualTo("a");
|
||||
|
||||
// Modify the value to invalidate the cache.
|
||||
Settings.Secure.putString(mMockContentResolver, SETTING, "b");
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
|
||||
|
||||
String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
|
||||
assertThat(returnedValue2).isEqualTo("b");
|
||||
|
||||
String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedValue2).isEqualTo("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching_multipleSettings() throws Exception {
|
||||
Settings.Secure.putString(mMockContentResolver, SETTING, "a");
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
|
||||
|
||||
String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
|
||||
assertThat(returnedValue).isEqualTo("a");
|
||||
|
||||
Settings.Secure.putString(mMockContentResolver, SETTING2, "b");
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_PUT_SECURE), any(), any(Bundle.class));
|
||||
|
||||
String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
|
||||
assertThat(returnedValue2).isEqualTo("b");
|
||||
|
||||
String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
// Modifying the second setting doesn't affect the cache of the first setting
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedValue).isEqualTo("a");
|
||||
|
||||
String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedValue2).isEqualTo("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching_nullSetting() throws Exception {
|
||||
String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
verify(mMockIContentProvider, times(1)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
|
||||
assertThat(returnedValue).isNull();
|
||||
|
||||
String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
|
||||
// Empty list won't be cached
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(),
|
||||
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
|
||||
assertThat(cachedValue).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ android_test {
|
||||
"test/**/*.java",
|
||||
"src/android/provider/settings/backup/*",
|
||||
"src/android/provider/settings/validators/*",
|
||||
"src/com/android/providers/settings/GenerationRegistry.java",
|
||||
"src/com/android/providers/settings/SettingsBackupAgent.java",
|
||||
"src/com/android/providers/settings/SettingsState.java",
|
||||
"src/com/android/providers/settings/SettingsHelper.java",
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
package com.android.providers.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.MemoryIntArray;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class tracks changes for config/global/secure/system tables
|
||||
* on a per user basis and updates shared memory regions which
|
||||
* on a per user basis and updates a shared memory region which
|
||||
* client processes can read to determine if their local caches are
|
||||
* stale.
|
||||
*/
|
||||
@@ -40,187 +40,138 @@ final class GenerationRegistry {
|
||||
|
||||
private final Object mLock;
|
||||
|
||||
// Key -> backingStore mapping
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
|
||||
|
||||
// Key -> (String->Index map) mapping
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
|
||||
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
|
||||
// Maximum number of backing stores allowed
|
||||
static final int NUM_MAX_BACKING_STORE = 8;
|
||||
private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private int mNumBackingStore = 0;
|
||||
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
|
||||
// Maximum size of an individual backing store
|
||||
static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
|
||||
private MemoryIntArray mBackingStore;
|
||||
|
||||
public GenerationRegistry(Object lock) {
|
||||
mLock = lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the generation number if the setting is already cached in the backing stores.
|
||||
* Otherwise, do nothing.
|
||||
*/
|
||||
public void incrementGeneration(int key, String name) {
|
||||
final boolean isConfig =
|
||||
(SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
|
||||
// Only store the prefix if the mutated setting is a config
|
||||
final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
|
||||
public void incrementGeneration(int key) {
|
||||
synchronized (mLock) {
|
||||
final MemoryIntArray backingStore = getBackingStoreLocked(key,
|
||||
/* createIfNotExist= */ false);
|
||||
if (backingStore == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
|
||||
backingStore, /* createIfNotExist= */ false);
|
||||
if (index < 0) {
|
||||
return;
|
||||
MemoryIntArray backingStore = getBackingStoreLocked();
|
||||
if (backingStore != null) {
|
||||
try {
|
||||
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
|
||||
if (index >= 0) {
|
||||
final int generation = backingStore.get(index) + 1;
|
||||
backingStore.set(index, generation);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error updating generation id", e);
|
||||
destroyBackingStore();
|
||||
}
|
||||
final int generation = backingStore.get(index) + 1;
|
||||
backingStore.set(index, generation);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
|
||||
+ " key:" + SettingsState.keyToString(key)
|
||||
+ " at index:" + index);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error updating generation id", e);
|
||||
destroyBackingStoreLocked(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the backing store's reference, the index and the current generation number
|
||||
* of a cached setting. If it was not in the backing store, first create the entry in it before
|
||||
* returning the result.
|
||||
*/
|
||||
public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
|
||||
public void addGenerationData(Bundle bundle, int key) {
|
||||
synchronized (mLock) {
|
||||
final MemoryIntArray backingStore = getBackingStoreLocked(key,
|
||||
/* createIfNotExist= */ true);
|
||||
if (backingStore == null) {
|
||||
// Error accessing existing backing store or no new backing store is available
|
||||
return;
|
||||
}
|
||||
MemoryIntArray backingStore = getBackingStoreLocked();
|
||||
try {
|
||||
final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
|
||||
backingStore, /* createIfNotExist= */ true);
|
||||
if (index < 0) {
|
||||
// Should not happen unless having error accessing the backing store
|
||||
return;
|
||||
}
|
||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
|
||||
backingStore);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
|
||||
backingStore.get(index));
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Exported index:" + index
|
||||
+ " for setting:" + indexMapKey
|
||||
+ " key:" + SettingsState.keyToString(key));
|
||||
if (backingStore != null) {
|
||||
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
|
||||
if (index >= 0) {
|
||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
|
||||
backingStore);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
|
||||
backingStore.get(index));
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
|
||||
+ SettingsProvider.keyToString(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error adding generation data", e);
|
||||
destroyBackingStoreLocked(key);
|
||||
destroyBackingStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onUserRemoved(int userId) {
|
||||
final int secureKey = SettingsState.makeKey(
|
||||
SettingsState.SETTINGS_TYPE_SECURE, userId);
|
||||
final int systemKey = SettingsState.makeKey(
|
||||
SettingsState.SETTINGS_TYPE_SYSTEM, userId);
|
||||
synchronized (mLock) {
|
||||
if (mKeyToIndexMapMap.containsKey(secureKey)) {
|
||||
destroyBackingStoreLocked(secureKey);
|
||||
mKeyToIndexMapMap.remove(secureKey);
|
||||
mNumBackingStore = mNumBackingStore - 1;
|
||||
}
|
||||
if (mKeyToIndexMapMap.containsKey(systemKey)) {
|
||||
destroyBackingStoreLocked(systemKey);
|
||||
mKeyToIndexMapMap.remove(systemKey);
|
||||
mNumBackingStore = mNumBackingStore - 1;
|
||||
MemoryIntArray backingStore = getBackingStoreLocked();
|
||||
if (backingStore != null && mKeyToIndexMap.size() > 0) {
|
||||
try {
|
||||
final int secureKey = SettingsProvider.makeKey(
|
||||
SettingsProvider.SETTINGS_TYPE_SECURE, userId);
|
||||
resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
|
||||
|
||||
final int systemKey = SettingsProvider.makeKey(
|
||||
SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
|
||||
resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error cleaning up for user", e);
|
||||
destroyBackingStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
|
||||
MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
|
||||
if (!createIfNotExist) {
|
||||
return backingStore;
|
||||
}
|
||||
if (backingStore == null) {
|
||||
private MemoryIntArray getBackingStoreLocked() {
|
||||
if (mBackingStore == null) {
|
||||
// One for the config table, one for the global table, two for system
|
||||
// and secure tables for a managed profile (managed profile is not
|
||||
// included in the max user count), ten for partially deleted users if
|
||||
// users are quickly removed, and twice max user count for system and
|
||||
// secure.
|
||||
final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
|
||||
try {
|
||||
if (mNumBackingStore >= NUM_MAX_BACKING_STORE) {
|
||||
Slog.e(LOG_TAG, "Error creating backing store - at capacity");
|
||||
return null;
|
||||
}
|
||||
backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
|
||||
mKeyToBackingStoreMap.put(key, backingStore);
|
||||
mNumBackingStore += 1;
|
||||
mBackingStore = new MemoryIntArray(size);
|
||||
if (DEBUG) {
|
||||
Slog.e(LOG_TAG, "Created backing store for "
|
||||
+ SettingsState.keyToString(key) + " on user: "
|
||||
+ SettingsState.getUserIdFromKey(key));
|
||||
Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error creating generation tracker", e);
|
||||
}
|
||||
}
|
||||
return backingStore;
|
||||
return mBackingStore;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void destroyBackingStoreLocked(int key) {
|
||||
MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
|
||||
if (backingStore != null) {
|
||||
private void destroyBackingStore() {
|
||||
if (mBackingStore != null) {
|
||||
try {
|
||||
backingStore.close();
|
||||
mBackingStore.close();
|
||||
if (DEBUG) {
|
||||
Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
|
||||
Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Cannot close generation memory array", e);
|
||||
}
|
||||
mKeyToBackingStoreMap.remove(key);
|
||||
mBackingStore = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getKeyIndexLocked(int key, String indexMapKey,
|
||||
ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
|
||||
MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
|
||||
ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
|
||||
if (nameToIndexMap == null) {
|
||||
if (!createIfNotExist) {
|
||||
return -1;
|
||||
private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
|
||||
MemoryIntArray backingStore) throws IOException {
|
||||
final int index = keyToIndexMap.get(key, -1);
|
||||
if (index >= 0) {
|
||||
keyToIndexMap.delete(key);
|
||||
backingStore.set(index, 0);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
|
||||
+ SettingsProvider.keyToString(key));
|
||||
}
|
||||
nameToIndexMap = new ArrayMap<>();
|
||||
keyToIndexMapMap.put(key, nameToIndexMap);
|
||||
}
|
||||
int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
|
||||
}
|
||||
|
||||
private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
|
||||
MemoryIntArray backingStore) throws IOException {
|
||||
int index = keyToIndexMap.get(key, -1);
|
||||
if (index < 0) {
|
||||
if (!createIfNotExist) {
|
||||
return -1;
|
||||
}
|
||||
index = findNextEmptyIndex(backingStore);
|
||||
if (index >= 0) {
|
||||
backingStore.set(index, 1);
|
||||
nameToIndexMap.put(indexMapKey, index);
|
||||
keyToIndexMap.append(key, index);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
|
||||
+ " of type:" + SettingsState.keyToString(key)
|
||||
+ " on user:" + SettingsState.getUserIdFromKey(key));
|
||||
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
|
||||
+ SettingsProvider.keyToString(key));
|
||||
}
|
||||
} else {
|
||||
Slog.e(LOG_TAG, "Could not allocate generation index");
|
||||
|
||||
@@ -35,7 +35,6 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OV
|
||||
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
|
||||
import static com.android.internal.accessibility.util.AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM;
|
||||
import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
|
||||
import static com.android.providers.settings.SettingsState.makeKey;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
@@ -376,6 +375,10 @@ public class SettingsProvider extends ContentProvider {
|
||||
@GuardedBy("mLock")
|
||||
private boolean mSyncConfigDisabledUntilReboot;
|
||||
|
||||
public static int makeKey(int type, int userId) {
|
||||
return SettingsState.makeKey(type, userId);
|
||||
}
|
||||
|
||||
public static int getTypeFromKey(int key) {
|
||||
return SettingsState.getTypeFromKey(key);
|
||||
}
|
||||
@@ -384,6 +387,9 @@ public class SettingsProvider extends ContentProvider {
|
||||
return SettingsState.getUserIdFromKey(key);
|
||||
}
|
||||
|
||||
public static String keyToString(int key) {
|
||||
return SettingsState.keyToString(key);
|
||||
}
|
||||
@ChangeId
|
||||
@EnabledSince(targetSdkVersion=android.os.Build.VERSION_CODES.S)
|
||||
private static final long ENFORCE_READ_PERMISSION_FOR_MULTI_SIM_DATA_CALL = 172670679L;
|
||||
@@ -547,7 +553,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
|
||||
case Settings.CALL_METHOD_LIST_CONFIG: {
|
||||
String prefix = getSettingPrefix(args);
|
||||
Bundle result = packageValuesForCallResult(prefix, getAllConfigFlags(prefix),
|
||||
Bundle result = packageValuesForCallResult(getAllConfigFlags(prefix),
|
||||
isTrackingGeneration(args));
|
||||
reportDeviceConfigAccess(prefix);
|
||||
return result;
|
||||
@@ -1313,7 +1319,6 @@ public class SettingsProvider extends ContentProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
|
||||
if (DEBUG) {
|
||||
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
|
||||
@@ -2311,8 +2316,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
"get/set setting for user", null);
|
||||
}
|
||||
|
||||
private Bundle packageValueForCallResult(@Nullable Setting setting,
|
||||
boolean trackingGeneration) {
|
||||
private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
|
||||
if (!trackingGeneration) {
|
||||
if (setting == null || setting.isNull()) {
|
||||
return NULL_SETTING_BUNDLE;
|
||||
@@ -2323,26 +2327,19 @@ public class SettingsProvider extends ContentProvider {
|
||||
result.putString(Settings.NameValueTable.VALUE,
|
||||
!setting.isNull() ? setting.getValue() : null);
|
||||
|
||||
if (setting != null && !setting.isNull()) {
|
||||
// No need to track generation if the setting doesn't exist
|
||||
synchronized (mLock) {
|
||||
mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey(),
|
||||
setting.getName());
|
||||
}
|
||||
}
|
||||
mSettingsRegistry.mGenerationRegistry.addGenerationData(result, setting.getKey());
|
||||
return result;
|
||||
}
|
||||
|
||||
private Bundle packageValuesForCallResult(String prefix,
|
||||
@NonNull HashMap<String, String> keyValues, boolean trackingGeneration) {
|
||||
private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
|
||||
boolean trackingGeneration) {
|
||||
Bundle result = new Bundle();
|
||||
result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
|
||||
if (trackingGeneration && !keyValues.isEmpty()) {
|
||||
// No need to track generation if the namespace is empty
|
||||
if (trackingGeneration) {
|
||||
synchronized (mLock) {
|
||||
mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
|
||||
mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
|
||||
UserHandle.USER_SYSTEM).mKey, prefix);
|
||||
mSettingsRegistry.getSettingsLocked(
|
||||
SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3452,7 +3449,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
|
||||
private void notifyForSettingsChange(int key, String name) {
|
||||
// Increment the generation first, so observers always see the new value
|
||||
mGenerationRegistry.incrementGeneration(key, name);
|
||||
mGenerationRegistry.incrementGeneration(key);
|
||||
|
||||
if (isGlobalSettingsKey(key) || isConfigSettingsKey(key)) {
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
@@ -3490,7 +3487,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
List<String> changedSettings) {
|
||||
|
||||
// Increment the generation first, so observers always see the new value
|
||||
mGenerationRegistry.incrementGeneration(key, prefix);
|
||||
mGenerationRegistry.incrementGeneration(key);
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(prefix);
|
||||
for (int i = 0; i < changedSettings.size(); ++i) {
|
||||
@@ -3516,7 +3513,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
if (profileId != userId) {
|
||||
final int key = makeKey(type, profileId);
|
||||
// Increment the generation first, so observers always see the new value
|
||||
mGenerationRegistry.incrementGeneration(key, name);
|
||||
mGenerationRegistry.incrementGeneration(key);
|
||||
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
|
||||
profileId, 0, uri).sendToTarget();
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.providers.settings;
|
||||
|
||||
import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY;
|
||||
import static android.provider.Settings.CALL_METHOD_GENERATION_KEY;
|
||||
import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.MemoryIntArray;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class GenerationRegistryTest {
|
||||
@Test
|
||||
public void testGenerationsWithRegularSetting() throws IOException {
|
||||
final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
|
||||
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
|
||||
final String testSecureSetting = "test_secure_setting";
|
||||
Bundle b = new Bundle();
|
||||
// IncrementGeneration should have no effect on a non-cached setting.
|
||||
generationRegistry.incrementGeneration(secureKey, testSecureSetting);
|
||||
generationRegistry.incrementGeneration(secureKey, testSecureSetting);
|
||||
generationRegistry.incrementGeneration(secureKey, testSecureSetting);
|
||||
generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
|
||||
// Default index is 0 and generation is only 1 despite early calls of incrementGeneration
|
||||
checkBundle(b, 0, 1, false);
|
||||
|
||||
generationRegistry.incrementGeneration(secureKey, testSecureSetting);
|
||||
generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
|
||||
// Index is still 0 and generation is now 2; also check direct array access
|
||||
assertThat(getArray(b).get(0)).isEqualTo(2);
|
||||
|
||||
final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0);
|
||||
final String testSystemSetting = "test_system_setting";
|
||||
generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
|
||||
// Default index is 0 and generation is 1 for another backingStore (system)
|
||||
checkBundle(b, 0, 1, false);
|
||||
|
||||
final String testSystemSetting2 = "test_system_setting2";
|
||||
generationRegistry.addGenerationData(b, systemKey, testSystemSetting2);
|
||||
// Second system setting index is 1 and default generation is 1
|
||||
checkBundle(b, 1, 1, false);
|
||||
|
||||
generationRegistry.incrementGeneration(systemKey, testSystemSetting);
|
||||
generationRegistry.incrementGeneration(systemKey, testSystemSetting);
|
||||
generationRegistry.addGenerationData(b, systemKey, testSystemSetting);
|
||||
// First system setting generation now incremented to 3
|
||||
checkBundle(b, 0, 3, false);
|
||||
|
||||
final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10);
|
||||
generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
|
||||
// User 10 has a new set of backingStores
|
||||
checkBundle(b, 0, 1, false);
|
||||
|
||||
// Check user removal
|
||||
generationRegistry.onUserRemoved(10);
|
||||
generationRegistry.incrementGeneration(systemKey2, testSystemSetting);
|
||||
|
||||
// Removed user should not affect existing caches
|
||||
generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
|
||||
assertThat(getArray(b).get(0)).isEqualTo(2);
|
||||
|
||||
// IncrementGeneration should have no effect for a non-cached user
|
||||
b.clear();
|
||||
checkBundle(b, -1, -1, true);
|
||||
// AddGeneration should create new backing store for the non-cached user
|
||||
generationRegistry.addGenerationData(b, systemKey2, testSystemSetting);
|
||||
checkBundle(b, 0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerationsWithConfigSetting() throws IOException {
|
||||
final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
|
||||
final String prefix = "test_namespace/";
|
||||
final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
|
||||
|
||||
Bundle b = new Bundle();
|
||||
generationRegistry.addGenerationData(b, configKey, prefix);
|
||||
checkBundle(b, 0, 1, false);
|
||||
|
||||
final String setting = "test_namespace/test_setting";
|
||||
// Check that the generation of the prefix is incremented correctly
|
||||
generationRegistry.incrementGeneration(configKey, setting);
|
||||
generationRegistry.addGenerationData(b, configKey, prefix);
|
||||
checkBundle(b, 0, 2, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxNumBackingStores() throws IOException {
|
||||
final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
|
||||
final String testSecureSetting = "test_secure_setting";
|
||||
Bundle b = new Bundle();
|
||||
for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) {
|
||||
b.clear();
|
||||
final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i);
|
||||
generationRegistry.addGenerationData(b, key, testSecureSetting);
|
||||
checkBundle(b, 0, 1, false);
|
||||
}
|
||||
b.clear();
|
||||
final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE,
|
||||
GenerationRegistry.NUM_MAX_BACKING_STORE + 1);
|
||||
generationRegistry.addGenerationData(b, key, testSecureSetting);
|
||||
// Should fail to add generation because the number of backing stores has reached limit
|
||||
checkBundle(b, -1, -1, true);
|
||||
// Remove one user should free up a backing store
|
||||
generationRegistry.onUserRemoved(0);
|
||||
generationRegistry.addGenerationData(b, key, testSecureSetting);
|
||||
checkBundle(b, 0, 1, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxSizeBackingStore() throws IOException {
|
||||
final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
|
||||
final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
|
||||
final String testSecureSetting = "test_secure_setting";
|
||||
Bundle b = new Bundle();
|
||||
for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) {
|
||||
generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i);
|
||||
checkBundle(b, i, 1, false);
|
||||
}
|
||||
b.clear();
|
||||
generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
|
||||
// Should fail to increase index because the number of entries in the backing store has
|
||||
// reached the limit
|
||||
checkBundle(b, -1, -1, true);
|
||||
// Shouldn't affect other cached entries
|
||||
generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0");
|
||||
checkBundle(b, 0, 1, false);
|
||||
}
|
||||
|
||||
private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
|
||||
throws IOException {
|
||||
final MemoryIntArray array = getArray(b);
|
||||
if (isNull) {
|
||||
assertThat(array).isNull();
|
||||
} else {
|
||||
assertThat(array).isNotNull();
|
||||
}
|
||||
final int index = b.getInt(
|
||||
CALL_METHOD_GENERATION_INDEX_KEY, -1);
|
||||
assertThat(index).isEqualTo(expectedIndex);
|
||||
final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1);
|
||||
assertThat(generation).isEqualTo(expectedGeneration);
|
||||
if (!isNull) {
|
||||
// Read into the result array with the result index should match the result generation
|
||||
assertThat(array.get(index)).isEqualTo(generation);
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryIntArray getArray(Bundle b) {
|
||||
return b.getParcelable(
|
||||
CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user