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 static final class GenerationTracker {
|
||||||
private final MemoryIntArray mArray;
|
private final MemoryIntArray mArray;
|
||||||
|
private final Runnable mErrorHandler;
|
||||||
private final int mIndex;
|
private final int mIndex;
|
||||||
private int mCurrentGeneration;
|
private int mCurrentGeneration;
|
||||||
|
|
||||||
public GenerationTracker(@NonNull MemoryIntArray array, int index) {
|
public GenerationTracker(@NonNull MemoryIntArray array, int index,
|
||||||
|
Runnable errorHandler) {
|
||||||
mArray = array;
|
mArray = array;
|
||||||
mIndex = index;
|
mIndex = index;
|
||||||
|
mErrorHandler = errorHandler;
|
||||||
mCurrentGeneration = readCurrentGeneration();
|
mCurrentGeneration = readCurrentGeneration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1487,9 +1490,23 @@ public final class Settings {
|
|||||||
return mArray.get(mIndex);
|
return mArray.get(mIndex);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error getting current generation", e);
|
Log.e(TAG, "Error getting current generation", e);
|
||||||
|
if (mErrorHandler != null) {
|
||||||
|
mErrorHandler.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
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.
|
// Thread-safe.
|
||||||
@@ -1616,7 +1633,20 @@ public final class Settings {
|
|||||||
+ cr.getPackageName() + " and user:"
|
+ cr.getPackageName() + " and user:"
|
||||||
+ userHandle + " with index:" + index);
|
+ 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);
|
mValues.put(name, value);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.os.Parcel;
|
|||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -46,6 +47,8 @@ import java.util.UUID;
|
|||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public final class MemoryIntArray implements Parcelable, Closeable {
|
public final class MemoryIntArray implements Parcelable, Closeable {
|
||||||
|
private static final String TAG = "MemoryIntArray";
|
||||||
|
|
||||||
private static final int MAX_SIZE = 1024;
|
private static final int MAX_SIZE = 1024;
|
||||||
|
|
||||||
private final int mOwnerPid;
|
private final int mOwnerPid;
|
||||||
@@ -142,8 +145,9 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if (!isClosed()) {
|
if (!isClosed()) {
|
||||||
nativeClose(mFd.getFd(), mMemoryAddr, isOwner());
|
ParcelFileDescriptor pfd = mFd;
|
||||||
mFd = null;
|
mFd = null;
|
||||||
|
nativeClose(pfd.getFd(), mMemoryAddr, isOwner());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +160,7 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
close();
|
IoUtils.closeQuietly(this);
|
||||||
super.finalize();
|
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 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 void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner);
|
||||||
private native int nativeSize(int fd);
|
private native int nativeSize(int fd);
|
||||||
private native static int nativeGetMemoryPageSize();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The max array size.
|
* @return The max array size.
|
||||||
@@ -246,7 +249,8 @@ public final class MemoryIntArray implements Parcelable, Closeable {
|
|||||||
try {
|
try {
|
||||||
return new MemoryIntArray(parcel);
|
return new MemoryIntArray(parcel);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new RuntimeException(ioe);
|
Log.e(TAG, "Error unparceling MemoryIntArray");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "core_jni_helpers.h"
|
#include "core_jni_helpers.h"
|
||||||
#include <cutils/ashmem.h>
|
#include <cutils/ashmem.h>
|
||||||
|
#include <linux/ashmem.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
@@ -44,11 +44,6 @@ static jint android_util_MemoryIntArray_create(JNIEnv* env, jobject clazz, jstri
|
|||||||
return -1;
|
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);
|
int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
|
||||||
if (setProtResult < 0) {
|
if (setProtResult < 0) {
|
||||||
jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool unpin = false;
|
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||||
|
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
||||||
if (!owner) {
|
return -1;
|
||||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
|
||||||
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
unpin = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
|
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
|
||||||
const int result = value->load(std::memory_order_relaxed);
|
return value->load(std::memory_order_relaxed);
|
||||||
|
|
||||||
if (unpin) {
|
|
||||||
ashmem_unpin_region(fd, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool unpin = false;
|
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
||||||
|
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
||||||
if (!owner) {
|
return;
|
||||||
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
|
|
||||||
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unpin = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
|
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
|
||||||
value->store(newValue, std::memory_order_relaxed);
|
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) {
|
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.
|
# Include all test java files.
|
||||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||||
|
LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl
|
||||||
|
|
||||||
LOCAL_STATIC_JAVA_LIBRARIES := \
|
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||||
android-support-test \
|
android-support-test \
|
||||||
|
|||||||
@@ -43,6 +43,11 @@
|
|||||||
|
|
||||||
<application>
|
<application>
|
||||||
<uses-library android:name="android.test.runner" />
|
<uses-library android:name="android.test.runner" />
|
||||||
|
|
||||||
|
<service android:name="android.util.RemoteMemoryIntArrayService"
|
||||||
|
android:process=":remote">
|
||||||
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<instrumentation
|
<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;
|
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 android.os.Parcel;
|
||||||
import junit.framework.TestCase;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import libcore.io.IoUtils;
|
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 {
|
public void testSize() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -32,6 +42,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testGetSet() throws Exception {
|
public void testGetSet() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -49,6 +60,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWritable() throws Exception {
|
public void testWritable() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -59,6 +71,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testClose() throws Exception {
|
public void testClose() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -72,6 +85,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testMarshalledGetSet() throws Exception {
|
public void testMarshalledGetSet() throws Exception {
|
||||||
MemoryIntArray firstArray = null;
|
MemoryIntArray firstArray = null;
|
||||||
MemoryIntArray secondArray = null;
|
MemoryIntArray secondArray = null;
|
||||||
@@ -99,6 +113,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testInteractOnceClosed() throws Exception {
|
public void testInteractOnceClosed() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -141,6 +156,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testInteractPutOfBounds() throws Exception {
|
public void testInteractPutOfBounds() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -178,6 +194,7 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testOverMaxSize() throws Exception {
|
public void testOverMaxSize() throws Exception {
|
||||||
MemoryIntArray array = null;
|
MemoryIntArray array = null;
|
||||||
try {
|
try {
|
||||||
@@ -189,4 +206,28 @@ public class MemoryIntArrayTest extends TestCase {
|
|||||||
IoUtils.closeQuietly(array);
|
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();
|
private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private final MemoryIntArray mImpl;
|
private MemoryIntArray mBackingStore;
|
||||||
|
|
||||||
public GenerationRegistry(Object lock) {
|
public GenerationRegistry(Object lock) {
|
||||||
mLock = 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) {
|
public void incrementGeneration(int key) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mImpl != null) {
|
MemoryIntArray backingStore = getBackingStoreLocked();
|
||||||
|
if (backingStore != null) {
|
||||||
try {
|
try {
|
||||||
final int index = getKeyIndexLocked(key);
|
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
final int generation = mImpl.get(index) + 1;
|
final int generation = backingStore.get(index) + 1;
|
||||||
mImpl.set(index, generation);
|
backingStore.set(index, generation);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Slog.e(LOG_TAG, "Error updating generation id", 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) {
|
public void addGenerationData(Bundle bundle, int key) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (mImpl != null) {
|
MemoryIntArray backingStore = getBackingStoreLocked();
|
||||||
final int index = getKeyIndexLocked(key);
|
try {
|
||||||
if (index >= 0) {
|
if (backingStore != null) {
|
||||||
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, mImpl);
|
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
|
||||||
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
|
if (index >= 0) {
|
||||||
if (DEBUG) {
|
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
|
||||||
Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
|
backingStore);
|
||||||
+ SettingsProvider.keyToString(key));
|
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) {
|
private MemoryIntArray getBackingStoreLocked() {
|
||||||
int index = mKeyToIndexMap.get(key, -1);
|
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) {
|
if (index < 0) {
|
||||||
index = findNextEmptyIndex();
|
index = findNextEmptyIndex(backingStore);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
try {
|
backingStore.set(index, 1);
|
||||||
mImpl.set(index, 1);
|
keyToIndexMap.append(key, index);
|
||||||
mKeyToIndexMap.append(key, index);
|
if (DEBUG) {
|
||||||
if (DEBUG) {
|
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
|
||||||
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
|
+ SettingsProvider.keyToString(key));
|
||||||
+ SettingsProvider.keyToString(key));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Slog.e(LOG_TAG, "Could not allocate generation index");
|
Slog.e(LOG_TAG, "Could not allocate generation index");
|
||||||
@@ -114,47 +168,13 @@ final class GenerationRegistry {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUserRemoved(int userId) {
|
private static int findNextEmptyIndex(MemoryIntArray backingStore) throws IOException {
|
||||||
synchronized (mLock) {
|
final int size = backingStore.size();
|
||||||
if (mImpl != null && mKeyToIndexMap.size() > 0) {
|
for (int i = 0; i < size; i++) {
|
||||||
final int secureKey = SettingsProvider.makeKey(
|
if (backingStore.get(i) == 0) {
|
||||||
SettingsProvider.SETTINGS_TYPE_SECURE, userId);
|
return i;
|
||||||
resetSlotForKeyLocked(secureKey);
|
|
||||||
|
|
||||||
final int systemKey = SettingsProvider.makeKey(
|
|
||||||
SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
|
|
||||||
resetSlotForKeyLocked(systemKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user