blob: 6e291c789b28b16bd954f6b4c1e443583269caf0 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.providers.settings;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.MemoryIntArray;
import android.util.Slog;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import java.io.IOException;
/**
* This class tracks changes for global/secure/system tables on a
* per user basis and updates a shared memory region which client
* processes can read to determine if their local caches are stale,
*/
final class GenerationRegistry {
private static final String LOG_TAG = "GenerationTracker";
private static final boolean DEBUG = false;
private final Object mLock;
@GuardedBy("mLock")
private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
@GuardedBy("mLock")
private MemoryIntArray mBackingStore;
public GenerationRegistry(Object lock) {
mLock = lock;
}
public void incrementGeneration(int key) {
synchronized (mLock) {
MemoryIntArray backingStore = getBackingStoreLocked();
if (backingStore != null) {
try {
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
if (index >= 0) {
final int generation = backingStore.get(index) + 1;
backingStore.set(index, generation);
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error updating generation id", e);
destroyBackingStore();
}
}
}
}
public void addGenerationData(Bundle bundle, int key) {
synchronized (mLock) {
MemoryIntArray backingStore = getBackingStoreLocked();
try {
if (backingStore != null) {
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
if (index >= 0) {
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
backingStore);
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
backingStore.get(index));
if (DEBUG) {
Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
}
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error adding generation data", e);
destroyBackingStore();
}
}
}
public void onUserRemoved(int userId) {
synchronized (mLock) {
MemoryIntArray backingStore = getBackingStoreLocked();
if (backingStore != null && mKeyToIndexMap.size() > 0) {
try {
final int secureKey = SettingsProvider.makeKey(
SettingsProvider.SETTINGS_TYPE_SECURE, userId);
resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
final int systemKey = SettingsProvider.makeKey(
SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error cleaning up for user", e);
destroyBackingStore();
}
}
}
}
private MemoryIntArray getBackingStoreLocked() {
if (mBackingStore == null) {
// One for the global table, two for system and secure tables for a
// managed profile (managed profile is not included in the max user
// count), ten for partially deleted users if users are quickly removed,
// and twice max user count for system and secure.
final int size = 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
try {
mBackingStore = new MemoryIntArray(size);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error creating generation tracker", e);
}
}
return mBackingStore;
}
private void destroyBackingStore() {
if (mBackingStore != null) {
try {
mBackingStore.close();
} catch (IOException e) {
Slog.e(LOG_TAG, "Cannot close generation memory array", e);
}
mBackingStore = null;
}
}
private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
MemoryIntArray backingStore) throws IOException {
final int index = keyToIndexMap.get(key, -1);
if (index >= 0) {
keyToIndexMap.delete(key);
backingStore.set(index, 0);
if (DEBUG) {
Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
}
}
private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
MemoryIntArray backingStore) throws IOException {
int index = keyToIndexMap.get(key, -1);
if (index < 0) {
index = findNextEmptyIndex(backingStore);
if (index >= 0) {
backingStore.set(index, 1);
keyToIndexMap.append(key, index);
if (DEBUG) {
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
} else {
Slog.e(LOG_TAG, "Could not allocate generation index");
}
}
return index;
}
private static int findNextEmptyIndex(MemoryIntArray backingStore) throws IOException {
final int size = backingStore.size();
for (int i = 0; i < size; i++) {
if (backingStore.get(i) == 0) {
return i;
}
}
return -1;
}
}