summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java5
-rw-r--r--core/java/android/app/ApplicationPackageManager.java34
-rw-r--r--core/java/android/content/pm/SystemFeaturesCache.java95
-rw-r--r--core/java/com/android/internal/os/ApplicationSharedMemory.java32
-rw-r--r--core/jni/com_android_internal_os_ApplicationSharedMemory.cpp82
-rw-r--r--core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java75
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java14
-rw-r--r--tests/Internal/Android.bp1
-rw-r--r--tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java62
9 files changed, 322 insertions, 78 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2d7ed46fe64a..8b1e2c06fd73 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -101,6 +101,7 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
import android.content.pm.ServiceInfo;
+import android.content.pm.SystemFeaturesCache;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -1341,6 +1342,10 @@ public final class ActivityThread extends ClientTransactionHandler
ApplicationSharedMemory instance =
ApplicationSharedMemory.fromFileDescriptor(
applicationSharedMemoryFd, /* mutable= */ false);
+ if (android.content.pm.Flags.cacheSdkSystemFeatures()) {
+ SystemFeaturesCache.setInstance(
+ new SystemFeaturesCache(instance.readSystemFeaturesCache()));
+ }
instance.closeFileDescriptor();
ApplicationSharedMemory.setInstance(instance);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2dead565fa85..3c4da764e473 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -79,6 +79,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SuspendDialogInfo;
+import android.content.pm.SystemFeaturesCache;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
@@ -804,16 +805,6 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public Boolean recompute(HasSystemFeatureQuery query) {
try {
- // As an optimization, check first to see if the feature was defined at
- // compile-time as either available or unavailable.
- // TODO(b/203143243): Consider hoisting this optimization out of the cache
- // after the trunk stable (build) flag has soaked and more features are
- // defined at compile-time.
- Boolean maybeHasSystemFeature =
- RoSystemFeatures.maybeHasFeature(query.name, query.version);
- if (maybeHasSystemFeature != null) {
- return maybeHasSystemFeature.booleanValue();
- }
return ActivityThread.currentActivityThread().getPackageManager().
hasSystemFeature(query.name, query.version);
} catch (RemoteException e) {
@@ -824,15 +815,28 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public boolean hasSystemFeature(String name, int version) {
+ // We check for system features in the following order:
+ // * Build time-defined system features (constant, very efficient)
+ // * SDK-defined system features (cached at process start, very efficient)
+ // * IPC-retrieved system features (lazily cached, requires per-feature IPC)
+ // TODO(b/375000483): Refactor all of this logic, including flag queries, into
+ // the SystemFeaturesCache class after initial rollout and validation.
+ Boolean maybeHasSystemFeature = RoSystemFeatures.maybeHasFeature(name, version);
+ if (maybeHasSystemFeature != null) {
+ return maybeHasSystemFeature;
+ }
+ if (com.android.internal.os.Flags.applicationSharedMemoryEnabled()
+ && android.content.pm.Flags.cacheSdkSystemFeatures()) {
+ maybeHasSystemFeature =
+ SystemFeaturesCache.getInstance().maybeHasFeature(name, version);
+ if (maybeHasSystemFeature != null) {
+ return maybeHasSystemFeature;
+ }
+ }
return mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version));
}
/** @hide */
- public void disableHasSystemFeatureCache() {
- mHasSystemFeatureCache.disableLocal();
- }
-
- /** @hide */
public static void invalidateHasSystemFeatureCache() {
mHasSystemFeatureCache.invalidateCache();
}
diff --git a/core/java/android/content/pm/SystemFeaturesCache.java b/core/java/android/content/pm/SystemFeaturesCache.java
index c41a7abbbc35..b3d70fa8bfaf 100644
--- a/core/java/android/content/pm/SystemFeaturesCache.java
+++ b/core/java/android/content/pm/SystemFeaturesCache.java
@@ -16,9 +16,8 @@
package android.content.pm;
+import android.annotation.MainThread;
import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.ArrayMap;
import com.android.internal.annotations.VisibleForTesting;
@@ -35,16 +34,53 @@ import java.util.Collection;
*
* @hide
*/
-public final class SystemFeaturesCache implements Parcelable {
+public final class SystemFeaturesCache {
// Sentinel value used for SDK-declared features that are unavailable on the current device.
private static final int UNAVAILABLE_FEATURE_VERSION = Integer.MIN_VALUE;
+ // This will be initialized just once, from the process main thread, but ready from any thread.
+ private static volatile SystemFeaturesCache sInstance;
+
// An array of versions for SDK-defined features, from [0, PackageManager.SDK_FEATURE_COUNT).
@NonNull
private final int[] mSdkFeatureVersions;
/**
+ * Installs the process-global cache instance.
+ *
+ * <p>Note: Usage should be gated on android.content.pm.Flags.cacheSdkSystemFeature(). In
+ * practice, this should only be called from 1) SystemServer init, or 2) bindApplication.
+ */
+ @MainThread
+ public static void setInstance(SystemFeaturesCache instance) {
+ if (sInstance != null) {
+ throw new IllegalStateException("SystemFeaturesCache instance already initialized.");
+ }
+ sInstance = instance;
+ }
+
+ /**
+ * Gets the process-global cache instance.
+ *
+ * Note: Usage should be gated on android.content.pm.Flags.cacheSdkSystemFeature(), and should
+ * always occur after the instance has been installed early in the process lifecycle.
+ */
+ public static @NonNull SystemFeaturesCache getInstance() {
+ SystemFeaturesCache instance = sInstance;
+ if (instance == null) {
+ throw new IllegalStateException("SystemFeaturesCache not initialized");
+ }
+ return instance;
+ }
+
+ /** Clears the process-global cache instance for testing. */
+ @VisibleForTesting
+ public static void clearInstance() {
+ sInstance = null;
+ }
+
+ /**
* Populates the cache from the set of all available {@link FeatureInfo} definitions.
*
* System features declared in {@link PackageManager} will be entered into the cache based on
@@ -69,20 +105,28 @@ public final class SystemFeaturesCache implements Parcelable {
}
}
- /** Only used by @{code CREATOR.createFromParcel(...)} */
- private SystemFeaturesCache(@NonNull Parcel parcel) {
- final int[] featureVersions = parcel.createIntArray();
- if (featureVersions == null) {
- throw new IllegalArgumentException(
- "Parceled SDK feature versions should never be null");
- }
- if (featureVersions.length != PackageManager.SDK_FEATURE_COUNT) {
+ /**
+ * Populates the cache from an array of SDK feature versions originally obtained via {@link
+ * #getSdkFeatureVersions()} from another instance.
+ */
+ public SystemFeaturesCache(@NonNull int[] sdkFeatureVersions) {
+ if (sdkFeatureVersions.length != PackageManager.SDK_FEATURE_COUNT) {
throw new IllegalArgumentException(
String.format(
"Unexpected cached SDK feature count: %d (expected %d)",
- featureVersions.length, PackageManager.SDK_FEATURE_COUNT));
+ sdkFeatureVersions.length, PackageManager.SDK_FEATURE_COUNT));
}
- mSdkFeatureVersions = featureVersions;
+ mSdkFeatureVersions = sdkFeatureVersions;
+ }
+
+ /**
+ * Gets the raw cached feature versions.
+ *
+ * <p>Note: This should generally only be neded for (de)serialization purposes.
+ */
+ // TODO(b/375000483): Consider reusing the ApplicationSharedMemory mapping for version lookup.
+ public int[] getSdkFeatureVersions() {
+ return mSdkFeatureVersions;
}
/**
@@ -105,29 +149,4 @@ public final class SystemFeaturesCache implements Parcelable {
return mSdkFeatureVersions[sdkFeatureIndex] >= version;
}
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeIntArray(mSdkFeatureVersions);
- }
-
- @NonNull
- public static final Parcelable.Creator<SystemFeaturesCache> CREATOR =
- new Parcelable.Creator<SystemFeaturesCache>() {
-
- @Override
- public SystemFeaturesCache createFromParcel(Parcel parcel) {
- return new SystemFeaturesCache(parcel);
- }
-
- @Override
- public SystemFeaturesCache[] newArray(int size) {
- return new SystemFeaturesCache[size];
- }
- };
}
diff --git a/core/java/com/android/internal/os/ApplicationSharedMemory.java b/core/java/com/android/internal/os/ApplicationSharedMemory.java
index e6ea29e483f1..4c491c82fd56 100644
--- a/core/java/com/android/internal/os/ApplicationSharedMemory.java
+++ b/core/java/com/android/internal/os/ApplicationSharedMemory.java
@@ -18,6 +18,7 @@ package com.android.internal.os;
import android.annotation.NonNull;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import dalvik.annotation.optimization.CriticalNative;
@@ -324,4 +325,35 @@ public class ApplicationSharedMemory implements AutoCloseable {
*/
@FastNative
private static native long nativeGetSystemNonceBlock(long ptr);
+
+ /**
+ * Perform a one-time write of cached SDK feature versions.
+ *
+ * @throws IllegalStateException if the feature versions have already been written or the ashmem
+ * is immutable.
+ * @throws IllegalArgumentException if the provided feature version array is too large.
+ */
+ public void writeSystemFeaturesCache(@NonNull int[] featureVersions) {
+ checkMutable();
+ nativeWriteSystemFeaturesCache(mPtr, featureVersions);
+ }
+
+ /**
+ * Read the cached SDK feature versions previously written to shared memory.
+ *
+ * Note: The result should generally be cached elsewhere for global reuse.
+ */
+ // TODO(b/326623529): Consider using a MappedByteBuffer or equivalent to avoid needing a
+ // Java copy of the cached data for potentially frequent reads. Alternatively, the JNI query
+ // lookup for a given feature could be cheap enough to avoid the cached Java copy entirely.
+ public @NonNull int[] readSystemFeaturesCache() {
+ checkMapped();
+ return nativeReadSystemFeaturesCache(mPtr);
+ }
+
+ @FastNative
+ private static native void nativeWriteSystemFeaturesCache(long ptr, int[] cache);
+
+ @FastNative
+ private static native int[] nativeReadSystemFeaturesCache(long ptr);
}
diff --git a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
index cc1687cd9ffb..71d169419cd3 100644
--- a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
+++ b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
@@ -23,18 +23,67 @@
#include <string.h>
#include <sys/mman.h>
+#include <array>
#include <atomic>
#include <cstddef>
#include <new>
-#include "core_jni_helpers.h"
-
#include "android_app_PropertyInvalidatedCache.h"
+#include "core_jni_helpers.h"
namespace {
using namespace android::app::PropertyInvalidatedCache;
+class alignas(8) SystemFeaturesCache {
+public:
+ // We only need enough space to handle the official set of SDK-defined system features (~200).
+ // TODO(b/326623529): Reuse the exact value defined by PackageManager.SDK_FEATURE_COUNT.
+ static constexpr int32_t kMaxSystemFeatures = 512;
+
+ void writeSystemFeatures(JNIEnv* env, jintArray jfeatures) {
+ if (featuresLength.load(std::memory_order_seq_cst) > 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "SystemFeaturesCache already written.");
+ return;
+ }
+
+ int32_t jfeaturesLength = env->GetArrayLength(jfeatures);
+ if (jfeaturesLength > kMaxSystemFeatures) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "SystemFeaturesCache only supports %d elements (vs %d requested).",
+ kMaxSystemFeatures, jfeaturesLength);
+ return;
+ }
+ env->GetIntArrayRegion(jfeatures, 0, jfeaturesLength, features.data());
+ featuresLength.store(jfeaturesLength, std::memory_order_seq_cst);
+ }
+
+ jintArray readSystemFeatures(JNIEnv* env) const {
+ jint jfeaturesLength = static_cast<jint>(featuresLength.load(std::memory_order_seq_cst));
+ jintArray jfeatures = env->NewIntArray(jfeaturesLength);
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ env->SetIntArrayRegion(jfeatures, 0, jfeaturesLength, features.data());
+ return jfeatures;
+ }
+
+private:
+ // A fixed length array of feature versions, with |featuresLength| dictating the actual size
+ // of features that have been written.
+ std::array<int32_t, kMaxSystemFeatures> features = {};
+ // The atomic acts as a barrier that precedes reads and follows writes, ensuring a
+ // consistent view of |features| across processes. Note that r/w synchronization *within* a
+ // process is handled at a higher level.
+ std::atomic<int64_t> featuresLength = 0;
+};
+
+static_assert(sizeof(SystemFeaturesCache) ==
+ sizeof(int32_t) * SystemFeaturesCache::kMaxSystemFeatures + sizeof(int64_t),
+ "Unexpected SystemFeaturesCache size");
+
// Atomics should be safe to use across processes if they are lock free.
static_assert(std::atomic<int64_t>::is_always_lock_free == true,
"atomic<int64_t> is not always lock free");
@@ -69,14 +118,25 @@ public:
latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = offset;
}
+ // The fixed size cache storage for SDK-defined system features.
+ SystemFeaturesCache systemFeaturesCache;
+
// The nonce storage for pic. The sizing is suitable for the system server module.
SystemCacheNonce systemPic;
};
-// Update the expected value when modifying the members of SharedMemory.
+// Update the expected values when modifying the members of SharedMemory.
// The goal of this assertion is to ensure that the data structure is the same size across 32-bit
// and 64-bit systems.
-static_assert(sizeof(SharedMemory) == 8 + sizeof(SystemCacheNonce), "Unexpected SharedMemory size");
+// TODO(b/396674280): Add an additional fixed size check for SystemCacheNonce after resolving
+// ABI discrepancies.
+static_assert(sizeof(SharedMemory) == 8 + sizeof(SystemFeaturesCache) + sizeof(SystemCacheNonce),
+ "Unexpected SharedMemory size");
+static_assert(offsetof(SharedMemory, systemFeaturesCache) == sizeof(int64_t),
+ "Unexpected SystemFeaturesCache offset in SharedMemory");
+static_assert(offsetof(SharedMemory, systemPic) ==
+ offsetof(SharedMemory, systemFeaturesCache) + sizeof(SystemFeaturesCache),
+ "Unexpected SystemCachceNonce offset in SharedMemory");
static jint nativeCreate(JNIEnv* env, jclass) {
// Create anonymous shared memory region
@@ -146,6 +206,16 @@ static jlong nativeGetSystemNonceBlock(JNIEnv*, jclass*, jlong ptr) {
return reinterpret_cast<jlong>(&sharedMemory->systemPic);
}
+static void nativeWriteSystemFeaturesCache(JNIEnv* env, jclass*, jlong ptr, jintArray jfeatures) {
+ SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
+ sharedMemory->systemFeaturesCache.writeSystemFeatures(env, jfeatures);
+}
+
+static jintArray nativeReadSystemFeaturesCache(JNIEnv* env, jclass*, jlong ptr) {
+ SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
+ return sharedMemory->systemFeaturesCache.readSystemFeatures(env);
+}
+
static const JNINativeMethod gMethods[] = {
{"nativeCreate", "()I", (void*)nativeCreate},
{"nativeMap", "(IZ)J", (void*)nativeMap},
@@ -156,7 +226,9 @@ static const JNINativeMethod gMethods[] = {
(void*)nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
{"nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(J)J",
(void*)nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
- {"nativeGetSystemNonceBlock", "(J)J", (void*) nativeGetSystemNonceBlock},
+ {"nativeGetSystemNonceBlock", "(J)J", (void*)nativeGetSystemNonceBlock},
+ {"nativeWriteSystemFeaturesCache", "(J[I)V", (void*)nativeWriteSystemFeaturesCache},
+ {"nativeReadSystemFeaturesCache", "(J)[I", (void*)nativeReadSystemFeaturesCache},
};
static const char kApplicationSharedMemoryClassName[] =
diff --git a/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java b/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java
index ce4aa42f39b6..8b513cb996b5 100644
--- a/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java
@@ -21,12 +21,16 @@ import static android.content.pm.PackageManager.FEATURE_WATCH;
import static com.google.common.truth.Truth.assertThat;
-import android.os.Parcel;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
import android.util.ArrayMap;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -36,6 +40,19 @@ public class SystemFeaturesCacheTest {
private SystemFeaturesCache mCache;
+ private SystemFeaturesCache mOriginalSingletonCache;
+
+ @Before
+ public void setUp() {
+ mOriginalSingletonCache = SystemFeaturesCache.getInstance();
+ }
+
+ @After
+ public void tearDown() {
+ SystemFeaturesCache.clearInstance();
+ SystemFeaturesCache.setInstance(mOriginalSingletonCache);
+ }
+
@Test
public void testNoFeatures() throws Exception {
SystemFeaturesCache cache = new SystemFeaturesCache(new ArrayMap<String, FeatureInfo>());
@@ -84,29 +101,57 @@ public class SystemFeaturesCacheTest {
}
@Test
- public void testParcel() throws Exception {
+ public void testGetAndSetFeatureVersions() throws Exception {
ArrayMap<String, FeatureInfo> features = new ArrayMap<>();
features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0));
SystemFeaturesCache cache = new SystemFeaturesCache(features);
- Parcel parcel = Parcel.obtain();
- SystemFeaturesCache parceledCache;
- try {
- parcel.writeParcelable(cache, 0);
- parcel.setDataPosition(0);
- parceledCache = parcel.readParcelable(getClass().getClassLoader());
- } finally {
- parcel.recycle();
- }
-
- assertThat(parceledCache.maybeHasFeature(FEATURE_WATCH, 0))
+ assertThat(cache.getSdkFeatureVersions().length)
+ .isEqualTo(PackageManager.SDK_FEATURE_COUNT);
+
+ SystemFeaturesCache clonedCache = new SystemFeaturesCache(cache.getSdkFeatureVersions());
+ assertThat(cache.getSdkFeatureVersions()).isEqualTo(clonedCache.getSdkFeatureVersions());
+
+ assertThat(clonedCache.maybeHasFeature(FEATURE_WATCH, 0))
.isEqualTo(cache.maybeHasFeature(FEATURE_WATCH, 0));
- assertThat(parceledCache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0))
+ assertThat(clonedCache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0))
.isEqualTo(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0));
- assertThat(parceledCache.maybeHasFeature("custom.feature", 0))
+ assertThat(clonedCache.maybeHasFeature("custom.feature", 0))
.isEqualTo(cache.maybeHasFeature("custom.feature", 0));
}
+ @Test
+ public void testInvalidFeatureVersions() throws Exception {
+ // Raw feature version arrays must match the predefined SDK feature count.
+ int[] invalidFeatureVersions = new int[PackageManager.SDK_FEATURE_COUNT - 1];
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new SystemFeaturesCache(invalidFeatureVersions));
+ }
+
+ @Test
+ public void testSingleton() throws Exception {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>();
+ features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0));
+ SystemFeaturesCache cache = new SystemFeaturesCache(features);
+
+ SystemFeaturesCache.clearInstance();
+ assertThrows(IllegalStateException.class, () -> SystemFeaturesCache.getInstance());
+
+ SystemFeaturesCache.setInstance(cache);
+ assertThat(SystemFeaturesCache.getInstance()).isEqualTo(cache);
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> SystemFeaturesCache.setInstance(new SystemFeaturesCache(features)));
+ }
+
+ @Test
+ public void testSingletonAutomaticallySetWithFeatureEnabled() {
+ assumeTrue(android.content.pm.Flags.cacheSdkSystemFeatures());
+ assertThat(SystemFeaturesCache.getInstance()).isNotNull();
+ }
+
private static FeatureInfo createFeature(String name, int version) {
FeatureInfo fi = new FeatureInfo();
fi.name = name;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0603c4506cd1..770ac31b2d55 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -301,6 +301,7 @@ import android.content.pm.ProviderInfoList;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SystemFeaturesCache;
import android.content.pm.TestUtilityService;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
@@ -2559,9 +2560,20 @@ public class ActivityManagerService extends IActivityManager.Stub
mTraceErrorLogger = new TraceErrorLogger();
mComponentAliasResolver = new ComponentAliasResolver(this);
sCreatorTokenCacheCleaner = new Handler(mHandlerThread.getLooper());
+
+ ApplicationSharedMemory applicationSharedMemory = ApplicationSharedMemory.getInstance();
+ if (android.content.pm.Flags.cacheSdkSystemFeatures()) {
+ // Install the cache into the process-wide singleton for in-proc queries, as well as
+ // shared memory. Apps will inflate the cache from shared memory in bindApplication.
+ SystemFeaturesCache systemFeaturesCache =
+ new SystemFeaturesCache(SystemConfig.getInstance().getAvailableFeatures());
+ SystemFeaturesCache.setInstance(systemFeaturesCache);
+ applicationSharedMemory.writeSystemFeaturesCache(
+ systemFeaturesCache.getSdkFeatureVersions());
+ }
try {
mApplicationSharedMemoryReadOnlyFd =
- ApplicationSharedMemory.getInstance().getReadOnlyFileDescriptor();
+ applicationSharedMemory.getReadOnlyFileDescriptor();
} catch (IOException e) {
Slog.e(TAG, "Failed to get read only fd for shared memory", e);
throw new RuntimeException(e);
diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp
index e294da101fb7..35564066266b 100644
--- a/tests/Internal/Android.bp
+++ b/tests/Internal/Android.bp
@@ -45,6 +45,7 @@ android_test {
"junit",
"androidx.test.rules",
"platform-test-annotations",
+ "truth",
],
manifest: "ApplicationSharedMemoryTest32/AndroidManifest.xml",
test_config: "ApplicationSharedMemoryTest32/AndroidTest.xml",
diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
index d03ad5cb2877..5ce0ede3e425 100644
--- a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
+++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java
@@ -16,24 +16,24 @@
package com.android.internal.os;
-import java.io.IOException;
+import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.junit.Before;
import java.io.FileDescriptor;
+import java.io.IOException;
/** Tests for {@link TimeoutRecord}. */
@SmallTest
@@ -77,6 +77,8 @@ public class ApplicationSharedMemoryTest {
try {
instance.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17);
fail("Attempted mutation in an app process should throw");
+ instance.writeSystemFeaturesCache(new int[] {1, 2, 3, 4, 5});
+ fail("Attempted feature mutation in an app process should throw");
} catch (Exception expected) {
}
}
@@ -121,4 +123,56 @@ public class ApplicationSharedMemoryTest {
} catch (Exception expected) {
}
}
+
+ /** If system feature caching is enabled, it should be auto-written into app shared memory. */
+ @Test
+ public void canReadSystemFeatures() throws IOException {
+ assumeTrue(android.content.pm.Flags.cacheSdkSystemFeatures());
+ ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance();
+ assertThat(instance.readSystemFeaturesCache()).isNotEmpty();
+ }
+
+ @Test
+ public void systemFeaturesShareMemory() throws IOException {
+ ApplicationSharedMemory instance1 = ApplicationSharedMemory.create();
+
+ int[] featureVersions = new int[] {1, 2, 3, 4, 5};
+ instance1.writeSystemFeaturesCache(featureVersions);
+ assertThat(featureVersions).isEqualTo(instance1.readSystemFeaturesCache());
+
+ ApplicationSharedMemory instance2 =
+ ApplicationSharedMemory.fromFileDescriptor(
+ instance1.getReadOnlyFileDescriptor(), /* mutable= */ false);
+ assertThat(featureVersions).isEqualTo(instance2.readSystemFeaturesCache());
+ }
+
+ @Test
+ public void systemFeaturesAreWriteOnce() throws IOException {
+ ApplicationSharedMemory instance1 = ApplicationSharedMemory.create();
+
+ try {
+ instance1.writeSystemFeaturesCache(new int[5000]);
+ fail("Cannot write an overly large system feature version buffer.");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ int[] featureVersions = new int[] {1, 2, 3, 4, 5};
+ instance1.writeSystemFeaturesCache(featureVersions);
+
+ int[] newFeatureVersions = new int[] {1, 2, 3, 4, 5, 6, 7};
+ try {
+ instance1.writeSystemFeaturesCache(newFeatureVersions);
+ fail("Cannot update system features after first write.");
+ } catch (IllegalStateException expected) {
+ }
+
+ ApplicationSharedMemory instance2 =
+ ApplicationSharedMemory.fromFileDescriptor(
+ instance1.getReadOnlyFileDescriptor(), /* mutable= */ false);
+ try {
+ instance2.writeSystemFeaturesCache(newFeatureVersions);
+ fail("Cannot update system features for read-only ashmem.");
+ } catch (IllegalStateException expected) {
+ }
+ }
}