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.os.Bundle;
20 import android.os.UserManager;
21 import android.provider.Settings;
22 import android.util.MemoryIntArray;
23 import android.util.Slog;
24 import android.util.SparseIntArray;
25 
26 import com.android.internal.annotations.GuardedBy;
27 
28 import java.io.IOException;
29 
30 /**
31  * This class tracks changes for config/global/secure/system tables
32  * on a per user basis and updates a shared memory region which
33  * client processes can read to determine if their local caches are
34  * stale.
35  */
36 final class GenerationRegistry {
37     private static final String LOG_TAG = "GenerationRegistry";
38 
39     private static final boolean DEBUG = false;
40 
41     private final Object mLock;
42 
43     @GuardedBy("mLock")
44     private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
45 
46     @GuardedBy("mLock")
47     private MemoryIntArray mBackingStore;
48 
GenerationRegistry(Object lock)49     public GenerationRegistry(Object lock) {
50         mLock = lock;
51     }
52 
incrementGeneration(int key)53     public void incrementGeneration(int key) {
54         synchronized (mLock) {
55             MemoryIntArray backingStore = getBackingStoreLocked();
56             if (backingStore != null) {
57                 try {
58                     final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
59                     if (index >= 0) {
60                         final int generation = backingStore.get(index) + 1;
61                         backingStore.set(index, generation);
62                     }
63                 } catch (IOException e) {
64                     Slog.e(LOG_TAG, "Error updating generation id", e);
65                     destroyBackingStore();
66                 }
67             }
68         }
69     }
70 
addGenerationData(Bundle bundle, int key)71     public void addGenerationData(Bundle bundle, int key) {
72         synchronized (mLock) {
73             MemoryIntArray backingStore = getBackingStoreLocked();
74             try {
75                 if (backingStore != null) {
76                     final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
77                     if (index >= 0) {
78                         bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
79                                 backingStore);
80                         bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
81                         bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
82                                 backingStore.get(index));
83                         if (DEBUG) {
84                             Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
85                                     + SettingsProvider.keyToString(key));
86                         }
87                     }
88                 }
89             } catch (IOException e) {
90                 Slog.e(LOG_TAG, "Error adding generation data", e);
91                 destroyBackingStore();
92             }
93         }
94     }
95 
onUserRemoved(int userId)96     public void onUserRemoved(int userId) {
97         synchronized (mLock) {
98             MemoryIntArray backingStore = getBackingStoreLocked();
99             if (backingStore != null && mKeyToIndexMap.size() > 0) {
100                 try {
101                     final int secureKey = SettingsProvider.makeKey(
102                             SettingsProvider.SETTINGS_TYPE_SECURE, userId);
103                     resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
104 
105                     final int systemKey = SettingsProvider.makeKey(
106                             SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
107                     resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
108                 } catch (IOException e) {
109                     Slog.e(LOG_TAG, "Error cleaning up for user", e);
110                     destroyBackingStore();
111                 }
112             }
113         }
114     }
115 
116     @GuardedBy("mLock")
getBackingStoreLocked()117     private MemoryIntArray getBackingStoreLocked() {
118         if (mBackingStore == null) {
119             // One for the config table, one for the global table, two for system
120             // and secure tables for a managed profile (managed profile is not
121             // included in the max user count), ten for partially deleted users if
122             // users are quickly removed, and twice max user count for system and
123             // secure.
124             final int size = 1 + 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
125             try {
126                 mBackingStore = new MemoryIntArray(size);
127                 if (DEBUG) {
128                     Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
129                 }
130             } catch (IOException e) {
131                 Slog.e(LOG_TAG, "Error creating generation tracker", e);
132             }
133         }
134         return mBackingStore;
135     }
136 
destroyBackingStore()137     private void destroyBackingStore() {
138         if (mBackingStore != null) {
139             try {
140                 mBackingStore.close();
141                 if (DEBUG) {
142                     Slog.e(LOG_TAG, "Destroyed backing store " + mBackingStore);
143                 }
144             } catch (IOException e) {
145                 Slog.e(LOG_TAG, "Cannot close generation memory array", e);
146             }
147             mBackingStore = null;
148         }
149     }
150 
resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap, MemoryIntArray backingStore)151     private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
152             MemoryIntArray backingStore) throws IOException {
153         final int index = keyToIndexMap.get(key, -1);
154         if (index >= 0) {
155             keyToIndexMap.delete(key);
156             backingStore.set(index, 0);
157             if (DEBUG) {
158                 Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
159                         + SettingsProvider.keyToString(key));
160             }
161         }
162     }
163 
getKeyIndexLocked(int key, SparseIntArray keyToIndexMap, MemoryIntArray backingStore)164     private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
165             MemoryIntArray backingStore) throws IOException {
166         int index = keyToIndexMap.get(key, -1);
167         if (index < 0) {
168             index = findNextEmptyIndex(backingStore);
169             if (index >= 0) {
170                 backingStore.set(index, 1);
171                 keyToIndexMap.append(key, index);
172                 if (DEBUG) {
173                     Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
174                             + SettingsProvider.keyToString(key));
175                 }
176             } else {
177                 Slog.e(LOG_TAG, "Could not allocate generation index");
178             }
179         }
180         return index;
181     }
182 
findNextEmptyIndex(MemoryIntArray backingStore)183     private static int findNextEmptyIndex(MemoryIntArray backingStore) throws IOException {
184         final int size = backingStore.size();
185         for (int i = 0; i < size; i++) {
186             if (backingStore.get(i) == 0) {
187                 return i;
188             }
189         }
190         return -1;
191     }
192 }