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 }