Merge "Make settings cahches generation mechanism robust." into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
b35301e421
@@ -1462,12 +1462,15 @@ public final class Settings {
|
||||
|
||||
private static final class GenerationTracker {
|
||||
private final MemoryIntArray mArray;
|
||||
private final Runnable mErrorHandler;
|
||||
private final int mIndex;
|
||||
private int mCurrentGeneration;
|
||||
|
||||
public GenerationTracker(@NonNull MemoryIntArray array, int index) {
|
||||
public GenerationTracker(@NonNull MemoryIntArray array, int index,
|
||||
Runnable errorHandler) {
|
||||
mArray = array;
|
||||
mIndex = index;
|
||||
mErrorHandler = errorHandler;
|
||||
mCurrentGeneration = readCurrentGeneration();
|
||||
}
|
||||
|
||||
@@ -1487,9 +1490,23 @@ public final class Settings {
|
||||
return mArray.get(mIndex);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error getting current generation", e);
|
||||
if (mErrorHandler != null) {
|
||||
mErrorHandler.run();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
try {
|
||||
mArray.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error closing backing array", e);
|
||||
if (mErrorHandler != null) {
|
||||
mErrorHandler.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thread-safe.
|
||||
@@ -1616,7 +1633,20 @@ public final class Settings {
|
||||
+ cr.getPackageName() + " and user:"
|
||||
+ userHandle + " with index:" + index);
|
||||
}
|
||||
mGenerationTracker = new GenerationTracker(array, index);
|
||||
mGenerationTracker = new GenerationTracker(array, index,
|
||||
() -> {
|
||||
synchronized (this) {
|
||||
Log.e(TAG, "Error accessing generation"
|
||||
+ " tracker - removing");
|
||||
if (mGenerationTracker != null) {
|
||||
GenerationTracker generationTracker =
|
||||
mGenerationTracker;
|
||||
mGenerationTracker = null;
|
||||
generationTracker.destroy();
|
||||
mValues.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
mValues.put(name, value);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@@ -46,6 +47,8 @@ import java.util.UUID;
|
||||
* @hide
|
||||
*/
|
||||
public final class MemoryIntArray implements Parcelable, Closeable {
|
||||
private static final String TAG = "MemoryIntArray";
|
||||
|
||||
private static final int MAX_SIZE = 1024;
|
||||
|
||||
private final int mOwnerPid;
|
||||
@@ -142,8 +145,9 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!isClosed()) {
|
||||
nativeClose(mFd.getFd(), mMemoryAddr, isOwner());
|
||||
ParcelFileDescriptor pfd = mFd;
|
||||
mFd = null;
|
||||
nativeClose(pfd.getFd(), mMemoryAddr, isOwner());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +160,7 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
close();
|
||||
IoUtils.closeQuietly(this);
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
@@ -230,7 +234,6 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
||||
private native int nativeGet(int fd, long memoryAddr, int index, boolean owner);
|
||||
private native void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner);
|
||||
private native int nativeSize(int fd);
|
||||
private native static int nativeGetMemoryPageSize();
|
||||
|
||||
/**
|
||||
* @return The max array size.
|
||||
@@ -246,7 +249,8 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
||||
try {
|
||||
return new MemoryIntArray(parcel);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
Log.e(TAG, "Error unparceling MemoryIntArray");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "core_jni_helpers.h"
|
||||
#include <cutils/ashmem.h>
|
||||
#include <linux/ashmem.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
namespace android {
|
||||
@@ -44,11 +44,6 @@ static jint android_util_MemoryIntArray_create(JNIEnv* env, jobject clazz, jstri
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||
jniThrowException(env, "java/io/IOException", "ashmem was purged");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
|
||||
if (setProtResult < 0) {
|
||||
jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
|
||||
@@ -133,24 +128,13 @@ static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool unpin = false;
|
||||
|
||||
if (!owner) {
|
||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
||||
return -1;
|
||||
}
|
||||
unpin = true;
|
||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
|
||||
const int result = value->load(std::memory_order_relaxed);
|
||||
|
||||
if (unpin) {
|
||||
ashmem_unpin_region(fd, 0, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
return value->load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
|
||||
@@ -161,22 +145,13 @@ static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
|
||||
return;
|
||||
}
|
||||
|
||||
bool unpin = false;
|
||||
|
||||
if (!owner) {
|
||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
||||
return;
|
||||
}
|
||||
unpin = true;
|
||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
||||
return;
|
||||
}
|
||||
|
||||
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
|
||||
value->store(newValue, std::memory_order_relaxed);
|
||||
|
||||
if (unpin) {
|
||||
ashmem_unpin_region(fd, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static jint android_util_MemoryIntArray_size(JNIEnv* env, jobject clazz, jint fd) {
|
||||
|
||||
@@ -10,6 +10,7 @@ LOCAL_MODULE_TAGS := tests
|
||||
|
||||
# Include all test java files.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
android-support-test \
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
|
||||
<service android:name="android.util.RemoteMemoryIntArrayService"
|
||||
android:process=":remote">
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 android.util;
|
||||
|
||||
import android.util.MemoryIntArray;
|
||||
|
||||
interface IRemoteMemoryIntArray {
|
||||
MemoryIntArray peekInstance();
|
||||
void create(int size, boolean clientWritable);
|
||||
boolean isWritable();
|
||||
int get(int index);
|
||||
void set(int index, int value);
|
||||
int size();
|
||||
void close();
|
||||
boolean isClosed();
|
||||
}
|
||||
@@ -16,12 +16,22 @@
|
||||
|
||||
package android.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.os.Parcel;
|
||||
import junit.framework.TestCase;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import libcore.io.IoUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
public class MemoryIntArrayTest extends TestCase {
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class MemoryIntArrayTest {
|
||||
|
||||
@Test
|
||||
public void testSize() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -32,6 +42,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSet() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -49,6 +60,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWritable() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -59,6 +71,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -72,6 +85,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarshalledGetSet() throws Exception {
|
||||
MemoryIntArray firstArray = null;
|
||||
MemoryIntArray secondArray = null;
|
||||
@@ -99,6 +113,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteractOnceClosed() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -141,6 +156,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteractPutOfBounds() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -178,6 +194,7 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverMaxSize() throws Exception {
|
||||
MemoryIntArray array = null;
|
||||
try {
|
||||
@@ -189,4 +206,28 @@ public class MemoryIntArrayTest extends TestCase {
|
||||
IoUtils.closeQuietly(array);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotMutableByUnprivilegedClients() throws Exception {
|
||||
RemoteIntArray remoteIntArray = new RemoteIntArray(1, false);
|
||||
try {
|
||||
assertNotNull("Couldn't get remote instance", remoteIntArray);
|
||||
MemoryIntArray localIntArray = remoteIntArray.peekInstance();
|
||||
assertNotNull("Couldn't get local instance", localIntArray);
|
||||
|
||||
remoteIntArray.set(0, 1);
|
||||
assertSame("Remote should be able to modify", 1, remoteIntArray.get(0));
|
||||
|
||||
try {
|
||||
localIntArray.set(0, 0);
|
||||
fail("Local shouldn't be able to modify");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
/* expected */
|
||||
}
|
||||
assertSame("Local shouldn't be able to modify", 1, localIntArray.get(0));
|
||||
assertSame("Local shouldn't be able to modify", 1, remoteIntArray.get(0));
|
||||
} finally {
|
||||
remoteIntArray.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
165
core/tests/utiltests/src/android/util/RemoteIntArray.java
Normal file
165
core/tests/utiltests/src/android/util/RemoteIntArray.java
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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 android.util;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
final class RemoteIntArray implements ServiceConnection, Closeable {
|
||||
private static final long BIND_REMOTE_SERVICE_TIMEOUT =
|
||||
("eng".equals(Build.TYPE)) ? 120000 : 10000;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private final Intent mIntent = new Intent();
|
||||
|
||||
private android.util.IRemoteMemoryIntArray mRemoteInstance;
|
||||
|
||||
public RemoteIntArray(int size, boolean clientWritable) throws IOException, TimeoutException {
|
||||
mIntent.setComponent(new ComponentName(InstrumentationRegistry.getContext(),
|
||||
RemoteMemoryIntArrayService.class));
|
||||
synchronized (mLock) {
|
||||
if (mRemoteInstance == null) {
|
||||
bindLocked();
|
||||
}
|
||||
try {
|
||||
mRemoteInstance.create(size, clientWritable);
|
||||
} catch (RemoteException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryIntArray peekInstance() {
|
||||
try {
|
||||
return mRemoteInstance.peekInstance();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isWritable() {
|
||||
try {
|
||||
return mRemoteInstance.isWritable();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int get(int index) throws IOException {
|
||||
try {
|
||||
return mRemoteInstance.get(index);
|
||||
} catch (RemoteException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int index, int value) throws IOException {
|
||||
try {
|
||||
mRemoteInstance.set(index, value);
|
||||
} catch (RemoteException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int size() throws IOException {
|
||||
try {
|
||||
return mRemoteInstance.size();
|
||||
} catch (RemoteException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
mRemoteInstance.close();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
try {
|
||||
return mRemoteInstance.isClosed();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindLocked() throws TimeoutException {
|
||||
if (mRemoteInstance != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
InstrumentationRegistry.getContext().bindService(mIntent, this, Context.BIND_AUTO_CREATE);
|
||||
|
||||
final long startMillis = SystemClock.uptimeMillis();
|
||||
while (true) {
|
||||
if (mRemoteInstance != null) {
|
||||
break;
|
||||
}
|
||||
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
|
||||
final long remainingMillis = BIND_REMOTE_SERVICE_TIMEOUT - elapsedMillis;
|
||||
if (remainingMillis <= 0) {
|
||||
throw new TimeoutException("Cannot get spooler!");
|
||||
}
|
||||
try {
|
||||
mLock.wait(remainingMillis);
|
||||
} catch (InterruptedException ie) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
mLock.notifyAll();
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
synchronized (mLock) {
|
||||
if (mRemoteInstance == null) {
|
||||
return;
|
||||
}
|
||||
mRemoteInstance = null;
|
||||
InstrumentationRegistry.getContext().unbindService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
synchronized (mLock) {
|
||||
mRemoteInstance = android.util.IRemoteMemoryIntArray.Stub.asInterface(service);
|
||||
mLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
synchronized (mLock) {
|
||||
mRemoteInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 android.util;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Service to interact with a {@link MemoryIntArray} in another process.
|
||||
*/
|
||||
public class RemoteMemoryIntArrayService extends Service {
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private MemoryIntArray mArray;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new android.util.IRemoteMemoryIntArray.Stub() {
|
||||
|
||||
@Override
|
||||
public void create(int size, boolean clientWritable) {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mArray = new MemoryIntArray(size, clientWritable);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryIntArray peekInstance() {
|
||||
synchronized (mLock) {
|
||||
return mArray;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable() {
|
||||
synchronized (mLock) {
|
||||
return mArray.isWritable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int index) {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
return mArray.get(index);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int index, int value) {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mArray.set(index, value);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
return mArray.size();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (mLock) {
|
||||
try {
|
||||
mArray.close();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
synchronized (mLock) {
|
||||
return mArray.isClosed();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -42,35 +42,25 @@ final class GenerationRegistry {
|
||||
private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final MemoryIntArray mImpl;
|
||||
private MemoryIntArray mBackingStore;
|
||||
|
||||
public GenerationRegistry(Object lock) {
|
||||
mLock = lock;
|
||||
// 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();
|
||||
MemoryIntArray impl = null;
|
||||
try {
|
||||
impl = new MemoryIntArray(size, false);
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error creating generation tracker", e);
|
||||
}
|
||||
mImpl = impl;
|
||||
}
|
||||
|
||||
public void incrementGeneration(int key) {
|
||||
synchronized (mLock) {
|
||||
if (mImpl != null) {
|
||||
MemoryIntArray backingStore = getBackingStoreLocked();
|
||||
if (backingStore != null) {
|
||||
try {
|
||||
final int index = getKeyIndexLocked(key);
|
||||
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
|
||||
if (index >= 0) {
|
||||
final int generation = mImpl.get(index) + 1;
|
||||
mImpl.set(index, generation);
|
||||
final int generation = backingStore.get(index) + 1;
|
||||
backingStore.set(index, generation);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error updating generation id", e);
|
||||
destroyBackingStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,34 +68,98 @@ final class GenerationRegistry {
|
||||
|
||||
public void addGenerationData(Bundle bundle, int key) {
|
||||
synchronized (mLock) {
|
||||
if (mImpl != null) {
|
||||
final int index = getKeyIndexLocked(key);
|
||||
if (index >= 0) {
|
||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, mImpl);
|
||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
|
||||
+ SettingsProvider.keyToString(key));
|
||||
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);
|
||||
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 int getKeyIndexLocked(int key) {
|
||||
int index = mKeyToIndexMap.get(key, -1);
|
||||
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, false);
|
||||
} 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();
|
||||
index = findNextEmptyIndex(backingStore);
|
||||
if (index >= 0) {
|
||||
try {
|
||||
mImpl.set(index, 1);
|
||||
mKeyToIndexMap.append(key, index);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
|
||||
+ SettingsProvider.keyToString(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
|
||||
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");
|
||||
@@ -114,47 +168,13 @@ final class GenerationRegistry {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void onUserRemoved(int userId) {
|
||||
synchronized (mLock) {
|
||||
if (mImpl != null && mKeyToIndexMap.size() > 0) {
|
||||
final int secureKey = SettingsProvider.makeKey(
|
||||
SettingsProvider.SETTINGS_TYPE_SECURE, userId);
|
||||
resetSlotForKeyLocked(secureKey);
|
||||
|
||||
final int systemKey = SettingsProvider.makeKey(
|
||||
SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
|
||||
resetSlotForKeyLocked(systemKey);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSlotForKeyLocked(int key) {
|
||||
final int index = mKeyToIndexMap.get(key, -1);
|
||||
if (index >= 0) {
|
||||
mKeyToIndexMap.delete(key);
|
||||
try {
|
||||
mImpl.set(index, 0);
|
||||
if (DEBUG) {
|
||||
Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
|
||||
+ SettingsProvider.keyToString(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int findNextEmptyIndex() {
|
||||
try {
|
||||
final int size = mImpl.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (mImpl.get(i) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Error reading generation memory array", e);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user