1 /*
2  * Copyright (C) 2016 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 android.annotation.NonNull;
20 import android.os.Bundle;
21 import android.os.UserHandle;
22 import android.provider.Settings;
23 import android.providers.settings.BackingStoreProto;
24 import android.providers.settings.CacheEntryProto;
25 import android.providers.settings.GenerationRegistryProto;
26 import android.util.ArrayMap;
27 import android.util.MemoryIntArray;
28 import android.util.Slog;
29 import android.util.proto.ProtoOutputStream;
30 
31 import com.android.internal.annotations.GuardedBy;
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.io.IOException;
35 import java.io.PrintWriter;
36 
37 /**
38  * This class tracks changes for config/global/secure/system tables
39  * on a per-user basis and updates shared memory regions which
40  * client processes can read to determine if their local caches are
41  * stale.
42  */
43 final class GenerationRegistry {
44     private static final String LOG_TAG = "GenerationRegistry";
45 
46     private static final boolean DEBUG = false;
47 
48     // This lock is not the same lock used in SettingsProvider and SettingsState
49     private final Object mLock = new Object();
50 
51     // Key -> backingStore mapping
52     @GuardedBy("mLock")
53     private final ArrayMap<Integer, MemoryIntArray> mKeyToBackingStoreMap = new ArrayMap();
54 
55     // Key -> (String->Index map) mapping
56     @GuardedBy("mLock")
57     private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>();
58 
59     @GuardedBy("mLock")
60     private int mNumBackingStore = 0;
61 
62     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
63     // Maximum size of an individual backing store
64     static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
65 
66     // Use an empty string to track the generation number of all non-predefined, unset settings
67     // The generation number is only increased when a new non-predefined setting is inserted
68     private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = "";
69 
70     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
71     // Minimum number of backing stores; supports 3 users
72     static final int MIN_NUM_BACKING_STORE = 8;
73     // Maximum number of backing stores; supports 18 users
74     static final int MAX_NUM_BACKING_STORE = 38;
75 
76     private final int mMaxNumBackingStore;
77 
GenerationRegistry(int maxNumUsers)78     GenerationRegistry(int maxNumUsers) {
79         // Add some buffer to maxNumUsers to accommodate corner cases when the actual number of
80         // users in the system exceeds the limit
81         maxNumUsers = maxNumUsers + 2;
82         // Number of backing stores needed for N users is (N + N + 1 + 1) = N * 2 + 2
83         // N Secure backing stores and N System backing stores, 1 Config and 1 Global for all users
84         // However, we always make sure that at least 3 users and at most 18 users are supported.
85         mMaxNumBackingStore = Math.min(Math.max(maxNumUsers * 2 + 2, MIN_NUM_BACKING_STORE),
86                 MAX_NUM_BACKING_STORE);
87     }
88 
89     /**
90      *  Increment the generation number if the setting is already cached in the backing stores.
91      *  Otherwise, do nothing.
92      */
incrementGeneration(int key, String name)93     public void incrementGeneration(int key, String name) {
94         final boolean isConfig =
95                 (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
96         // Only store the prefix if the mutated setting is a config
97         final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
98         incrementGenerationInternal(key, indexMapKey);
99     }
100 
incrementGenerationInternal(int key, @NonNull String indexMapKey)101     private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
102         if (SettingsState.isGlobalSettingsKey(key)) {
103             // Global settings are shared across users, so ignore the userId in the key
104             key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
105         }
106         synchronized (mLock) {
107             final MemoryIntArray backingStore = getBackingStoreLocked(key,
108                     /* createIfNotExist= */ false);
109             if (backingStore == null) {
110                 return;
111             }
112             try {
113                 final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
114                         backingStore, /* createIfNotExist= */ false);
115                 if (index < 0) {
116                     return;
117                 }
118                 final int generation = backingStore.get(index) + 1;
119                 backingStore.set(index, generation);
120                 if (DEBUG) {
121                     Slog.i(LOG_TAG, "Incremented generation for "
122                             + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
123                             + " key:" + SettingsState.keyToString(key)
124                             + " at index:" + index);
125                 }
126             } catch (IOException e) {
127                 Slog.e(LOG_TAG, "Error updating generation id", e);
128                 destroyBackingStoreLocked(key);
129             }
130         }
131     }
132 
133     // A new, non-predefined setting has been inserted, increment the tracking number for all unset
134     // settings
incrementGenerationForUnsetSettings(int key)135     public void incrementGenerationForUnsetSettings(int key) {
136         final boolean isConfig =
137                 (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
138         if (isConfig) {
139             // No need to track new settings for configs
140             return;
141         }
142         incrementGenerationInternal(key, DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
143     }
144 
145     /**
146      *  Return the backing store's reference, the index and the current generation number
147      *  of a cached setting. If it was not in the backing store, first create the entry in it before
148      *  returning the result.
149      */
addGenerationData(Bundle bundle, int key, String indexMapKey)150     public void addGenerationData(Bundle bundle, int key, String indexMapKey) {
151         if (SettingsState.isGlobalSettingsKey(key)) {
152             // Global settings are shared across users, so ignore the userId in the key
153             key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
154         }
155         synchronized (mLock) {
156             final MemoryIntArray backingStore = getBackingStoreLocked(key,
157                     /* createIfNotExist= */ true);
158             if (backingStore == null) {
159                 // Error accessing existing backing store or no new backing store is available
160                 return;
161             }
162             try {
163                 final int index = getKeyIndexLocked(key, indexMapKey, mKeyToIndexMapMap,
164                         backingStore, /* createIfNotExist= */ true);
165                 if (index < 0) {
166                     // Should not happen unless having error accessing the backing store
167                     return;
168                 }
169                 bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, backingStore);
170                 bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
171                 bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, backingStore.get(index));
172                 if (DEBUG) {
173                     Slog.i(LOG_TAG, "Exported index:" + index + " for "
174                             + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
175                             + " key:" + SettingsState.keyToString(key));
176                 }
177             } catch (IOException e) {
178                 Slog.e(LOG_TAG, "Error adding generation data", e);
179                 destroyBackingStoreLocked(key);
180             }
181         }
182     }
183 
addGenerationDataForUnsetSettings(Bundle bundle, int key)184     public void addGenerationDataForUnsetSettings(Bundle bundle, int key) {
185         addGenerationData(bundle, key, /* indexMapKey= */ DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
186     }
187 
onUserRemoved(int userId)188     public void onUserRemoved(int userId) {
189         final int secureKey = SettingsState.makeKey(
190                 SettingsState.SETTINGS_TYPE_SECURE, userId);
191         final int systemKey = SettingsState.makeKey(
192                 SettingsState.SETTINGS_TYPE_SYSTEM, userId);
193         synchronized (mLock) {
194             if (mKeyToIndexMapMap.containsKey(secureKey)) {
195                 destroyBackingStoreLocked(secureKey);
196                 mKeyToIndexMapMap.remove(secureKey);
197                 mNumBackingStore = mNumBackingStore - 1;
198             }
199             if (mKeyToIndexMapMap.containsKey(systemKey)) {
200                 destroyBackingStoreLocked(systemKey);
201                 mKeyToIndexMapMap.remove(systemKey);
202                 mNumBackingStore = mNumBackingStore - 1;
203             }
204         }
205     }
206 
207     @GuardedBy("mLock")
getBackingStoreLocked(int key, boolean createIfNotExist)208     private MemoryIntArray getBackingStoreLocked(int key, boolean createIfNotExist) {
209         MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
210         if (!createIfNotExist) {
211             return backingStore;
212         }
213         if (backingStore == null) {
214             try {
215                 if (mNumBackingStore >= mMaxNumBackingStore) {
216                     if (DEBUG) {
217                         Slog.e(LOG_TAG, "Error creating backing store - at capacity");
218                     }
219                     return null;
220                 }
221                 backingStore = new MemoryIntArray(MAX_BACKING_STORE_SIZE);
222                 mKeyToBackingStoreMap.put(key, backingStore);
223                 mNumBackingStore += 1;
224                 if (DEBUG) {
225                     Slog.e(LOG_TAG, "Created backing store for "
226                             + SettingsState.keyToString(key) + " on user: "
227                             + SettingsState.getUserIdFromKey(key));
228                 }
229             } catch (IOException e) {
230                 Slog.e(LOG_TAG, "Error creating generation tracker", e);
231             }
232         }
233         return backingStore;
234     }
235 
236     @GuardedBy("mLock")
destroyBackingStoreLocked(int key)237     private void destroyBackingStoreLocked(int key) {
238         MemoryIntArray backingStore = mKeyToBackingStoreMap.get(key);
239         if (backingStore != null) {
240             try {
241                 backingStore.close();
242                 if (DEBUG) {
243                     Slog.e(LOG_TAG, "Destroyed backing store " + backingStore);
244                 }
245             } catch (IOException e) {
246                 Slog.e(LOG_TAG, "Cannot close generation memory array", e);
247             }
248             mKeyToBackingStoreMap.remove(key);
249         }
250     }
251 
getKeyIndexLocked(int key, String indexMapKey, ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap, MemoryIntArray backingStore, boolean createIfNotExist)252     private static int getKeyIndexLocked(int key, String indexMapKey,
253             ArrayMap<Integer, ArrayMap<String, Integer>> keyToIndexMapMap,
254             MemoryIntArray backingStore, boolean createIfNotExist) throws IOException {
255         ArrayMap<String, Integer> nameToIndexMap = keyToIndexMapMap.get(key);
256         if (nameToIndexMap == null) {
257             if (!createIfNotExist) {
258                 return -1;
259             }
260             nameToIndexMap = new ArrayMap<>();
261             keyToIndexMapMap.put(key, nameToIndexMap);
262         }
263         int index = nameToIndexMap.getOrDefault(indexMapKey, -1);
264         if (index < 0) {
265             if (!createIfNotExist) {
266                 return -1;
267             }
268             index = findNextEmptyIndex(backingStore);
269             if (index >= 0) {
270                 backingStore.set(index, 1);
271                 nameToIndexMap.put(indexMapKey, index);
272                 if (DEBUG) {
273                     Slog.i(LOG_TAG, "Allocated index:" + index + " for setting:" + indexMapKey
274                             + " of type:" + SettingsState.keyToString(key)
275                             + " on user:" + SettingsState.getUserIdFromKey(key));
276                 }
277             } else {
278                 if (DEBUG) {
279                     Slog.e(LOG_TAG, "Could not allocate generation index");
280                 }
281             }
282         }
283         return index;
284     }
285 
findNextEmptyIndex(MemoryIntArray backingStore)286     private static int findNextEmptyIndex(MemoryIntArray backingStore) throws IOException {
287         final int size = backingStore.size();
288         for (int i = 0; i < size; i++) {
289             if (backingStore.get(i) == 0) {
290                 return i;
291             }
292         }
293         return -1;
294     }
295 
296     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
getMaxNumBackingStores()297     int getMaxNumBackingStores() {
298         return mMaxNumBackingStore;
299     }
300 
dumpProto(ProtoOutputStream proto)301     public void dumpProto(ProtoOutputStream proto) {
302         synchronized (mLock) {
303             final int numBackingStores = mKeyToBackingStoreMap.size();
304             proto.write(GenerationRegistryProto.NUM_BACKING_STORES, numBackingStores);
305             proto.write(GenerationRegistryProto.NUM_MAX_BACKING_STORES, getMaxNumBackingStores());
306 
307             for (int i = 0; i < numBackingStores; i++) {
308                 final long token = proto.start(GenerationRegistryProto.BACKING_STORES);
309                 final int key = mKeyToBackingStoreMap.keyAt(i);
310                 proto.write(BackingStoreProto.KEY, key);
311                 proto.write(BackingStoreProto.BACKING_STORE_SIZE,
312                         mKeyToBackingStoreMap.valueAt(i).size());
313                 proto.write(BackingStoreProto.NUM_CACHED_ENTRIES,
314                         mKeyToIndexMapMap.get(key).size());
315                 final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
316                 final MemoryIntArray backingStore = getBackingStoreLocked(key,
317                         /* createIfNotExist= */ false);
318                 if (indexMap == null || backingStore == null) {
319                     continue;
320                 }
321                 for (String setting : indexMap.keySet()) {
322                     try {
323                         final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
324                                 backingStore, /* createIfNotExist= */ false);
325                         if (index < 0) {
326                             continue;
327                         }
328                         final long cacheEntryToken = proto.start(
329                                 BackingStoreProto.CACHE_ENTRIES);
330                         final int generation = backingStore.get(index);
331                         proto.write(CacheEntryProto.NAME,
332                                 setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
333                                         ? "UNSET" : setting);
334                         proto.write(CacheEntryProto.GENERATION, generation);
335                         proto.end(cacheEntryToken);
336                     } catch (IOException e) {
337                         throw new RuntimeException(e);
338                     }
339                 }
340                 proto.end(token);
341             }
342 
343         }
344     }
345 
dump(PrintWriter pw)346     public void dump(PrintWriter pw) {
347         pw.println("GENERATION REGISTRY");
348         pw.println("Maximum number of backing stores:" + getMaxNumBackingStores());
349         synchronized (mLock) {
350             final int numBackingStores = mKeyToBackingStoreMap.size();
351             pw.println("Number of backing stores:" + numBackingStores);
352             for (int i = 0; i < numBackingStores; i++) {
353                 final int key = mKeyToBackingStoreMap.keyAt(i);
354                 pw.print("_Backing store for type:"); pw.print(SettingsState.settingTypeToString(
355                         SettingsState.getTypeFromKey(key)));
356                 pw.print(" user:"); pw.print(SettingsState.getUserIdFromKey(key));
357                 pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size());
358                 pw.println(" cachedEntries:" + mKeyToIndexMapMap.get(key).size());
359                 final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
360                 final MemoryIntArray backingStore = getBackingStoreLocked(key,
361                         /* createIfNotExist= */ false);
362                 if (indexMap == null || backingStore == null) {
363                     continue;
364                 }
365                 for (String setting : indexMap.keySet()) {
366                     try {
367                         final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
368                                 backingStore, /* createIfNotExist= */ false);
369                         if (index < 0) {
370                             continue;
371                         }
372                         final int generation = backingStore.get(index);
373                         pw.print("  setting: "); pw.print(
374                                 setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
375                                         ? "UNSET" : setting);
376                         pw.println(" generation:" + generation);
377                     } catch (IOException e) {
378                         throw new RuntimeException(e);
379                     }
380                 }
381             }
382         }
383     }
384 }