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