1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.settings; 18 19 import static android.provider.Settings.CALL_METHOD_GENERATION_INDEX_KEY; 20 import static android.provider.Settings.CALL_METHOD_GENERATION_KEY; 21 import static android.provider.Settings.CALL_METHOD_TRACK_GENERATION_KEY; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import android.os.Bundle; 26 import android.util.MemoryIntArray; 27 28 import androidx.test.runner.AndroidJUnit4; 29 30 import org.junit.Test; 31 import org.junit.runner.RunWith; 32 33 import java.io.IOException; 34 35 @RunWith(AndroidJUnit4.class) 36 public class GenerationRegistryTest { 37 @Test testGenerationsWithRegularSetting()38 public void testGenerationsWithRegularSetting() throws IOException { 39 final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); 40 final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); 41 final String testSecureSetting = "test_secure_setting"; 42 Bundle b = new Bundle(); 43 // IncrementGeneration should have no effect on a non-cached setting. 44 generationRegistry.incrementGeneration(secureKey, testSecureSetting); 45 generationRegistry.incrementGeneration(secureKey, testSecureSetting); 46 generationRegistry.incrementGeneration(secureKey, testSecureSetting); 47 generationRegistry.addGenerationData(b, secureKey, testSecureSetting); 48 // Default index is 0 and generation is only 1 despite early calls of incrementGeneration 49 checkBundle(b, 0, 1, false); 50 51 generationRegistry.incrementGeneration(secureKey, testSecureSetting); 52 generationRegistry.addGenerationData(b, secureKey, testSecureSetting); 53 // Index is still 0 and generation is now 2; also check direct array access 54 assertThat(getArray(b).get(0)).isEqualTo(2); 55 56 final int systemKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 0); 57 final String testSystemSetting = "test_system_setting"; 58 generationRegistry.addGenerationData(b, systemKey, testSystemSetting); 59 // Default index is 0 and generation is 1 for another backingStore (system) 60 checkBundle(b, 0, 1, false); 61 62 final String testSystemSetting2 = "test_system_setting2"; 63 generationRegistry.addGenerationData(b, systemKey, testSystemSetting2); 64 // Second system setting index is 1 and default generation is 1 65 checkBundle(b, 1, 1, false); 66 67 generationRegistry.incrementGeneration(systemKey, testSystemSetting); 68 generationRegistry.incrementGeneration(systemKey, testSystemSetting); 69 generationRegistry.addGenerationData(b, systemKey, testSystemSetting); 70 // First system setting generation now incremented to 3 71 checkBundle(b, 0, 3, false); 72 73 final int systemKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SYSTEM, 10); 74 generationRegistry.addGenerationData(b, systemKey2, testSystemSetting); 75 // User 10 has a new set of backingStores 76 checkBundle(b, 0, 1, false); 77 78 // Check user removal 79 generationRegistry.onUserRemoved(10); 80 generationRegistry.incrementGeneration(systemKey2, testSystemSetting); 81 82 // Removed user should not affect existing caches 83 generationRegistry.addGenerationData(b, secureKey, testSecureSetting); 84 assertThat(getArray(b).get(0)).isEqualTo(2); 85 86 // IncrementGeneration should have no effect for a non-cached user 87 b.clear(); 88 checkBundle(b, -1, -1, true); 89 // AddGeneration should create new backing store for the non-cached user 90 generationRegistry.addGenerationData(b, systemKey2, testSystemSetting); 91 checkBundle(b, 0, 1, false); 92 } 93 94 @Test testGenerationsWithConfigSetting()95 public void testGenerationsWithConfigSetting() throws IOException { 96 final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); 97 final String prefix = "test_namespace/"; 98 final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); 99 100 Bundle b = new Bundle(); 101 generationRegistry.addGenerationData(b, configKey, prefix); 102 checkBundle(b, 0, 1, false); 103 104 final String setting = "test_namespace/test_setting"; 105 // Check that the generation of the prefix is incremented correctly 106 generationRegistry.incrementGeneration(configKey, setting); 107 generationRegistry.addGenerationData(b, configKey, prefix); 108 checkBundle(b, 0, 2, false); 109 } 110 111 @Test testMaxNumBackingStores()112 public void testMaxNumBackingStores() throws IOException { 113 final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); 114 final String testSecureSetting = "test_secure_setting"; 115 Bundle b = new Bundle(); 116 for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) { 117 b.clear(); 118 final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i); 119 generationRegistry.addGenerationData(b, key, testSecureSetting); 120 checkBundle(b, 0, 1, false); 121 } 122 b.clear(); 123 final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 124 generationRegistry.getMaxNumBackingStores() + 1); 125 generationRegistry.addGenerationData(b, key, testSecureSetting); 126 // Should fail to add generation because the number of backing stores has reached limit 127 checkBundle(b, -1, -1, true); 128 // Remove one user should free up a backing store 129 generationRegistry.onUserRemoved(0); 130 generationRegistry.addGenerationData(b, key, testSecureSetting); 131 checkBundle(b, 0, 1, false); 132 } 133 134 @Test testMaxSizeBackingStore()135 public void testMaxSizeBackingStore() throws IOException { 136 final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); 137 final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); 138 final String testSecureSetting = "test_secure_setting"; 139 Bundle b = new Bundle(); 140 for (int i = 0; i < GenerationRegistry.MAX_BACKING_STORE_SIZE; i++) { 141 generationRegistry.addGenerationData(b, secureKey, testSecureSetting + i); 142 checkBundle(b, i, 1, false); 143 } 144 b.clear(); 145 generationRegistry.addGenerationData(b, secureKey, testSecureSetting); 146 // Should fail to increase index because the number of entries in the backing store has 147 // reached the limit 148 checkBundle(b, -1, -1, true); 149 // Shouldn't affect other cached entries 150 generationRegistry.addGenerationData(b, secureKey, testSecureSetting + "0"); 151 checkBundle(b, 0, 1, false); 152 } 153 154 @Test testUnsetSettings()155 public void testUnsetSettings() throws IOException { 156 final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); 157 final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); 158 final String testSecureSetting = "test_secure_setting"; 159 Bundle b = new Bundle(); 160 generationRegistry.addGenerationData(b, secureKey, testSecureSetting); 161 checkBundle(b, 0, 1, false); 162 generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); 163 checkBundle(b, 1, 1, false); 164 generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); 165 // Test that unset settings always have the same index 166 checkBundle(b, 1, 1, false); 167 generationRegistry.incrementGenerationForUnsetSettings(secureKey); 168 // Test that the generation number of the unset settings have increased 169 generationRegistry.addGenerationDataForUnsetSettings(b, secureKey); 170 checkBundle(b, 1, 2, false); 171 } 172 173 @Test testGlobalSettings()174 public void testGlobalSettings() throws IOException { 175 final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); 176 final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0); 177 final String testGlobalSetting = "test_global_setting"; 178 final Bundle b = new Bundle(); 179 generationRegistry.addGenerationData(b, globalKey, testGlobalSetting); 180 checkBundle(b, 0, 1, false); 181 final MemoryIntArray array = getArray(b); 182 final int globalKey2 = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 10); 183 b.clear(); 184 generationRegistry.addGenerationData(b, globalKey2, testGlobalSetting); 185 checkBundle(b, 0, 1, false); 186 final MemoryIntArray array2 = getArray(b); 187 // Check that user10 and user0 use the same array to store global settings' generations 188 assertThat(array).isEqualTo(array2); 189 } 190 191 @Test testNumberOfBackingStores()192 public void testNumberOfBackingStores() { 193 GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0); 194 // Test that the capacity of the backing stores is always valid 195 assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( 196 GenerationRegistry.MIN_NUM_BACKING_STORE); 197 generationRegistry = new GenerationRegistry(new Object(), 100); 198 // Test that the capacity of the backing stores is always valid 199 assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( 200 GenerationRegistry.MAX_NUM_BACKING_STORE); 201 } 202 checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)203 private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull) 204 throws IOException { 205 final MemoryIntArray array = getArray(b); 206 if (isNull) { 207 assertThat(array).isNull(); 208 } else { 209 assertThat(array).isNotNull(); 210 } 211 final int index = b.getInt( 212 CALL_METHOD_GENERATION_INDEX_KEY, -1); 213 assertThat(index).isEqualTo(expectedIndex); 214 final int generation = b.getInt(CALL_METHOD_GENERATION_KEY, -1); 215 assertThat(generation).isEqualTo(expectedGeneration); 216 if (!isNull) { 217 // Read into the result array with the result index should match the result generation 218 assertThat(array.get(index)).isEqualTo(generation); 219 } 220 } 221 getArray(Bundle b)222 private MemoryIntArray getArray(Bundle b) { 223 return b.getParcelable( 224 CALL_METHOD_TRACK_GENERATION_KEY, android.util.MemoryIntArray.class); 225 } 226 } 227