diff options
| author | 2025-02-07 01:11:46 +0000 | |
|---|---|---|
| committer | 2025-02-15 00:01:18 +0000 | |
| commit | b47d056cc247edab3cb4716457f224cc1ee8ffb0 (patch) | |
| tree | f8f1b3e71a4ae4e58c1b76393dcf1acecc3a324a | |
| parent | a5c29f6c42b9203eeb198a004633384e5fd2196b (diff) | |
Integrate SDK system feature cache behind a flag
Add the SDK system feature cache to the hasSystemFeature query lookup.
This involves populating the initial cache array in system server, and
handing this as a compact int array (<1KB) to each process via
ApplicationSharedMemory.
Currently, the compact array is copied into Java heap for easy and fast
access. Follow-up work will explore reusing the mapped buffer and
relying on JNI and/or MappedByteBuffer for zero-copy access.
Flag: android.content.pm.cache_sdk_system_features
Bug: 326623529
Test: presubmit
Change-Id: I905e82eb7ae3a6714cb3751280b927f8bb379358
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) { + } + } } |