Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4e09642d authored by Linus Tufvesson's avatar Linus Tufvesson Committed by Android (Google) Code Review
Browse files

Merge "Cache empty prefixs in NameValueCache"

parents 6100cdb6 bc3e94fd
Loading
Loading
Loading
Loading
+19 −13
Original line number Diff line number Diff line
@@ -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);
+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();
    }

}