Loading core/java/android/provider/Settings.java +19 −13 Original line number Diff line number Diff line Loading @@ -2721,6 +2721,7 @@ public final class Settings { } } }); currentGeneration = generation; } } if (mGenerationTracker != null && currentGeneration == Loading Loading @@ -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) { Loading @@ -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)); } } Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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( Loading Loading @@ -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); core/tests/coretests/src/android/provider/NameValueCacheTest.java 0 → 100644 +232 −0 Original line number Diff line number Diff line /* * 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(); } } Loading
core/java/android/provider/Settings.java +19 −13 Original line number Diff line number Diff line Loading @@ -2721,6 +2721,7 @@ public final class Settings { } } }); currentGeneration = generation; } } if (mGenerationTracker != null && currentGeneration == Loading Loading @@ -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) { Loading @@ -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)); } } Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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( Loading Loading @@ -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);
core/tests/coretests/src/android/provider/NameValueCacheTest.java 0 → 100644 +232 −0 Original line number Diff line number Diff line /* * 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(); } }