/* * 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(), 2); 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(), 1); 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(), 2); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); 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.getMaxNumBackingStores() + 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(), 1); 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); } @Test public void testUnsetSettings() throws IOException { final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); generationRegistry.addGenerationData(b, secureKey, testSecureSetting); checkBundle(b, 0, 1, false); generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); checkBundle(b, 1, 1, false); generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); // Test that unset settings always have the same index checkBundle(b, 1, 1, false); generationRegistry.incrementGenerationForUnsetSettings(secureKey); // Test that the generation number of the unset settings have increased generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); checkBundle(b, 1, 2, false); } @Test public void testGlobalSettings() throws IOException { final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0); final String testGlobalSetting = "test_global_setting"; final Bundle b = new Bundle(); generationRegistry.addGenerationData(b, globalKey, testGlobalSetting); checkBundle(b, 0, 1, false); final MemoryIntArray array = getArray(b); final int globalKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 10); b.clear(); generationRegistry.addGenerationData(b, globalKey2, testGlobalSetting); checkBundle(b, 0, 1, false); final MemoryIntArray array2 = getArray(b); // Check that user10 and user0 use the same array to store global settings' generations assertThat(array).isEqualTo(array2); } @Test public void testNumberOfBackingStores() { GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0); // Test that the capacity of the backing stores is always valid assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( GenerationRegistry.MIN_NUM_BACKING_STORE); generationRegistry = new GenerationRegistry(new Object(), 100); // Test that the capacity of the backing stores is always valid assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( GenerationRegistry.MAX_NUM_BACKING_STORE); } 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); } }