Merge "Cache empty prefixs in NameValueCache"
This commit is contained in:
committed by
Android (Google) Code Review
commit
4e09642d50
@@ -2721,6 +2721,7 @@ public final class Settings {
|
||||
}
|
||||
}
|
||||
});
|
||||
currentGeneration = generation;
|
||||
}
|
||||
}
|
||||
if (mGenerationTracker != null && currentGeneration ==
|
||||
@@ -2801,14 +2802,7 @@ public final class Settings {
|
||||
}
|
||||
mValues.clear();
|
||||
} else {
|
||||
boolean prefixCached = false;
|
||||
int size = mValues.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (mValues.keyAt(i).startsWith(prefix)) {
|
||||
prefixCached = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
boolean prefixCached = mValues.containsKey(prefix);
|
||||
if (prefixCached) {
|
||||
if (!names.isEmpty()) {
|
||||
for (String name : names) {
|
||||
@@ -2817,9 +2811,11 @@ public final class Settings {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < size; ++i) {
|
||||
for (int i = 0; i < mValues.size(); ++i) {
|
||||
String key = mValues.keyAt(i);
|
||||
if (key.startsWith(prefix)) {
|
||||
// Explicitly exclude the prefix as it is only there to
|
||||
// signal that the prefix has been cached.
|
||||
if (key.startsWith(prefix) && !key.equals(prefix)) {
|
||||
keyValues.put(key, mValues.get(key));
|
||||
}
|
||||
}
|
||||
@@ -2907,12 +2903,15 @@ public final class Settings {
|
||||
}
|
||||
}
|
||||
});
|
||||
currentGeneration = generation;
|
||||
}
|
||||
}
|
||||
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.
|
||||
mValues.put(prefix, null);
|
||||
}
|
||||
}
|
||||
return keyValues;
|
||||
@@ -14104,7 +14103,7 @@ public final class Settings {
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
|
||||
static Map<String, String> getStrings(@NonNull ContentResolver resolver,
|
||||
public static Map<String, String> getStrings(@NonNull ContentResolver resolver,
|
||||
@NonNull String namespace, @NonNull List<String> names) {
|
||||
List<String> compositeNames = new ArrayList<>(names.size());
|
||||
for (String name : names) {
|
||||
@@ -14163,8 +14162,9 @@ public final class Settings {
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
|
||||
static boolean setStrings(@NonNull ContentResolver resolver, @NonNull String namespace,
|
||||
@NonNull Map<String, String> keyValues) throws DeviceConfig.BadConfigException {
|
||||
public static boolean setStrings(@NonNull ContentResolver resolver,
|
||||
@NonNull String namespace, @NonNull Map<String, String> keyValues)
|
||||
throws DeviceConfig.BadConfigException {
|
||||
HashMap<String, String> compositeKeyValueMap = new HashMap<>(keyValues.keySet().size());
|
||||
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
|
||||
compositeKeyValueMap.put(
|
||||
@@ -14241,6 +14241,12 @@ public final class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static void clearProviderForTest() {
|
||||
sProviderHolder.clearProviderForTest();
|
||||
sNameValueCache.clearGenerationTrackerForTest();
|
||||
}
|
||||
|
||||
private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
|
||||
Preconditions.checkNotNull(namespace);
|
||||
Preconditions.checkNotNull(name);
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.provider;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.IContentProvider;
|
||||
import android.os.Bundle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.test.mock.MockContentResolver;
|
||||
import android.util.MemoryIntArray;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* These tests are verifying that Settings#NameValueCache is working as expected with DeviceConfig.
|
||||
* Due to how the classes are structured, we have to test it in a somewhat roundabout way. We're
|
||||
* mocking out the contentProvider and are handcrafting very specific Bundles to answer the queries.
|
||||
*/
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class NameValueCacheTest {
|
||||
|
||||
private static final String NAMESPACE = "namespace";
|
||||
private static final String NAMESPACE2 = "namespace2";
|
||||
|
||||
@Mock
|
||||
private IContentProvider mMockIContentProvider;
|
||||
@Mock
|
||||
private ContentProvider mMockContentProvider;
|
||||
private MockContentResolver mMockContentResolver;
|
||||
private MemoryIntArray mCacheGenerationStore;
|
||||
private int mCurrentGeneration = 123;
|
||||
|
||||
private HashMap<String, HashMap<String, String>> mStorage;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Settings.Config.clearProviderForTest();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
|
||||
mMockContentResolver = new MockContentResolver();
|
||||
mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(),
|
||||
mMockContentProvider);
|
||||
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(), any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
|
||||
Bundle incomingBundle = invocationOnMock.getArgument(5);
|
||||
HashMap<String, String> keyValues =
|
||||
(HashMap<String, String>) incomingBundle.getSerializable(
|
||||
Settings.CALL_METHOD_FLAGS_KEY);
|
||||
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
|
||||
mStorage.put(prefix, keyValues);
|
||||
mCacheGenerationStore.set(0, ++mCurrentGeneration);
|
||||
|
||||
Bundle result = new Bundle();
|
||||
result.putBoolean(Settings.KEY_CONFIG_SET_RETURN, true);
|
||||
return result;
|
||||
});
|
||||
|
||||
// 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 caller asked
|
||||
// for one.
|
||||
when(mMockIContentProvider.call(any(), any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
|
||||
Bundle incomingBundle = invocationOnMock.getArgument(5);
|
||||
|
||||
String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY);
|
||||
|
||||
if (!mStorage.containsKey(prefix)) {
|
||||
mStorage.put(prefix, new HashMap<>());
|
||||
}
|
||||
HashMap<String, String> keyValues = mStorage.get(prefix);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues);
|
||||
|
||||
if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
|
||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
|
||||
mCacheGenerationStore);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
|
||||
mCacheGenerationStore.get(0));
|
||||
}
|
||||
return bundle;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching_singleNamespace() throws Exception {
|
||||
HashMap<String, String> keyValues = new HashMap<>();
|
||||
keyValues.put("a", "b");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
|
||||
verify(mMockIContentProvider).call(any(), 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(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
// Modify the value to invalidate the cache.
|
||||
keyValues.put("a", "c");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(), any(),
|
||||
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(), any(),
|
||||
eq(Settings.CALL_METHOD_LIST_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
assertThat(returnedValues2).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE, Collections.emptyList());
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching_multipleNamespaces() throws Exception {
|
||||
HashMap<String, String> keyValues = new HashMap<>();
|
||||
keyValues.put("a", "b");
|
||||
Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues);
|
||||
verify(mMockIContentProvider).call(any(), any(), any(),
|
||||
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
|
||||
any(), any(Bundle.class));
|
||||
|
||||
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(), 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(), 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());
|
||||
verify(mMockIContentProvider, times(2)).call(any(), any(), any(),
|
||||
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());
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues);
|
||||
|
||||
Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE2, Collections.emptyList());
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaching_emptyNamespace() throws Exception {
|
||||
Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver,
|
||||
NAMESPACE,
|
||||
Collections.emptyList());
|
||||
verify(mMockIContentProvider).call(any(), 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());
|
||||
verifyNoMoreInteractions(mMockIContentProvider);
|
||||
assertThat(cachedKeyValues).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user