Loading core/java/android/app/PropertyInvalidatedCache.java +236 −48 Original line number Diff line number Diff line Loading @@ -32,7 +32,10 @@ import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -92,9 +95,6 @@ public class PropertyInvalidatedCache<Query, Result> { * caching on behalf of other processes. */ public boolean shouldBypassCache(@NonNull Q query) { if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) { return Binder.getCallingUid() != Process.myUid(); } return false; } }; Loading Loading @@ -392,8 +392,213 @@ public class PropertyInvalidatedCache<Query, Result> { } } /** * An array of hash maps, indexed by calling UID. The class behaves a bit like a hash map * except that it uses the calling UID internally. */ private class CacheMap<Query, Result> { // Create a new map for a UID, using the parent's configuration for max size. private LinkedHashMap<Query, Result> createMap() { return new LinkedHashMap<Query, Result>( 2 /* start small */, 0.75f /* default load factor */, true /* LRU access order */) { @GuardedBy("mLock") @Override protected boolean removeEldestEntry(Map.Entry eldest) { final int size = size(); if (size > mHighWaterMark) { mHighWaterMark = size; } if (size > mMaxEntries) { mMissOverflow++; return true; } return false; } }; } // An array of maps, indexed by UID. private final SparseArray<LinkedHashMap<Query, Result>> mCache = new SparseArray<>(); // If true, isolate the hash entries by calling UID. If this is false, allow the cache // entries to be combined in a single hash map. private final boolean mIsolated; // Collect statistics. private final boolean mStatistics; // An array of booleans to indicate if a UID has been involved in a map access. A value // exists for every UID that was ever involved during cache access. This is updated only // if statistics are being collected. private final SparseBooleanArray mUidSeen; // A hash map that ignores the UID. This is used in look-aside fashion just for hit/miss // statistics. This is updated only if statistics are being collected. private final ArraySet<Query> mShadowCache; // Shadow statistics. Only hits and misses need to be recorded. These are updated only // if statistics are being collected. The "SelfHits" records hits when the UID is the // process uid. private int mShadowHits; private int mShadowMisses; private int mShadowSelfHits; // The process UID. private final int mSelfUid; // True in test mode. In test mode, the cache uses Binder.getWorkSource() as the UID. private final boolean mTestMode; /** * Create a CacheMap. UID isolation is enabled if the input parameter is true and if the * isolation feature is enabled. */ CacheMap(boolean isolate, boolean testMode) { mIsolated = Flags.picIsolateCacheByUid() && isolate; mStatistics = Flags.picIsolatedCacheStatistics() && mIsolated; if (mStatistics) { mUidSeen = new SparseBooleanArray(); mShadowCache = new ArraySet<>(); } else { mUidSeen = null; mShadowCache = null; } mSelfUid = Process.myUid(); mTestMode = testMode; } // Return the UID for this cache invocation. If uid isolation is disabled, the value of 0 // is returned, which effectively places all entries in a single hash map. private int callerUid() { if (!mIsolated) { return 0; } else if (mTestMode) { return Binder.getCallingWorkSourceUid(); } else { return Binder.getCallingUid(); } } /** * Lookup an entry in the cache. */ Result get(Query query) { final int uid = callerUid(); // Shadow statistics if (mStatistics) { if (mShadowCache.contains(query)) { mShadowHits++; if (uid == mSelfUid) { mShadowSelfHits++; } } else { mShadowMisses++; } } var map = mCache.get(uid); if (map != null) { return map.get(query); } else { return null; } } /** * Remove an entry from the cache. */ void remove(Query query) { final int uid = callerUid(); if (mStatistics) { mShadowCache.remove(query); } var map = mCache.get(uid); if (map != null) { map.remove(query); } } /** * Record an entry in the cache. */ void put(Query query, Result result) { final int uid = callerUid(); if (mStatistics) { mShadowCache.add(query); mUidSeen.put(uid, true); } var map = mCache.get(uid); if (map == null) { map = createMap(); mCache.put(uid, map); } map.put(query, result); } /** * Return the number of entries in the cache. */ int size() { int total = 0; for (int i = 0; i < mCache.size(); i++) { var map = mCache.valueAt(i); total += map.size(); } return total; } /** * Clear the entries in the cache. Update the shadow statistics. */ void clear() { if (mStatistics) { mShadowCache.clear(); } mCache.clear(); } // Dump basic statistics, if any are collected. Do nothing if statistics are not enabled. void dump(PrintWriter pw) { if (mStatistics) { pw.println(formatSimple(" ShadowHits: %d, ShadowMisses: %d, ShadowSize: %d", mShadowHits, mShadowMisses, mShadowCache.size())); pw.println(formatSimple(" ShadowUids: %d, SelfUid: %d", mUidSeen.size(), mShadowSelfHits)); } } // Dump detailed statistics void dumpDetailed(PrintWriter pw) { for (int i = 0; i < mCache.size(); i++) { int uid = mCache.keyAt(i); var map = mCache.valueAt(i); Set<Map.Entry<Query, Result>> cacheEntries = map.entrySet(); if (cacheEntries.size() == 0) { break; } pw.println(" Contents:"); pw.println(formatSimple(" Uid: %d\n", uid)); for (Map.Entry<Query, Result> entry : cacheEntries) { String key = Objects.toString(entry.getKey()); String value = Objects.toString(entry.getValue()); pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value)); } } } } @GuardedBy("mLock") private final LinkedHashMap<Query, Result> mCache; private final CacheMap<Query, Result> mCache; /** * The nonce handler for this cache. Loading Loading @@ -895,7 +1100,8 @@ public class PropertyInvalidatedCache<Query, Result> { * is allowed to be null in the record constructor to facility reuse of Args instances. * @hide */ public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) { public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries, boolean mIsolateUids, boolean mTestMode) { // Validation: the module must be one of the known module strings and the maxEntries must // be positive. Loading @@ -909,15 +1115,28 @@ public class PropertyInvalidatedCache<Query, Result> { // which is not legal, but there is no reasonable default. Clients must call the api // method to set the field properly. public Args(@NonNull String module) { this(module, /* api */ null, /* maxEntries */ 32); this(module, null, // api 32, // maxEntries true, // isolateUids false // testMode ); } public Args api(@NonNull String api) { return new Args(mModule, api, mMaxEntries); return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode); } public Args maxEntries(int val) { return new Args(mModule, mApi, val); return new Args(mModule, mApi, val, mIsolateUids, mTestMode); } public Args isolateUids(boolean val) { return new Args(mModule, mApi, mMaxEntries, val, mTestMode); } public Args testMode(boolean val) { return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val); } } Loading @@ -936,7 +1155,7 @@ public class PropertyInvalidatedCache<Query, Result> { mCacheName = cacheName; mNonce = getNonceHandler(mPropertyName); mMaxEntries = args.mMaxEntries; mCache = createMap(); mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode); mComputer = (computer != null) ? computer : new DefaultComputer<>(this); registerCache(); } Loading Loading @@ -1006,28 +1225,6 @@ public class PropertyInvalidatedCache<Query, Result> { this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer); } // Create a map. This should be called only from the constructor. private LinkedHashMap<Query, Result> createMap() { return new LinkedHashMap<Query, Result>( 2 /* start small */, 0.75f /* default load factor */, true /* LRU access order */) { @GuardedBy("mLock") @Override protected boolean removeEldestEntry(Map.Entry eldest) { final int size = size(); if (size > mHighWaterMark) { mHighWaterMark = size; } if (size > mMaxEntries) { mMissOverflow++; return true; } return false; } }; } /** * Register the map in the global list. If the cache is disabled globally, disable it * now. This method is only ever called from the constructor, which means no other thread has Loading Loading @@ -1778,8 +1975,8 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(formatSimple(" Cache Name: %s", cacheName())); pw.println(formatSimple(" Property: %s", mPropertyName)); pw.println(formatSimple( " Hits: %d, Misses: %d, Skips: %d, Clears: %d", mHits, mMisses, getSkipsLocked(), mClears)); " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d", mHits, mMisses, getSkipsLocked(), mClears, mCache.size())); // Print all the skip reasons. pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]); Loading @@ -1794,25 +1991,16 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(formatSimple( " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d", mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); mCache.dump(pw); pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); // No specific cache was requested. This is the default, and no details // should be dumped. if (!detailed) { return; } Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet(); if (cacheEntries.size() == 0) { return; // Dump the contents of the cache. if (detailed) { mCache.dumpDetailed(pw); } pw.println(" Contents:"); for (Map.Entry<Query, Result> entry : cacheEntries) { String key = Objects.toString(entry.getKey()); String value = Objects.toString(entry.getValue()); pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value)); } // Separator between caches. pw.println(""); } } Loading core/java/android/app/performance.aconfig +17 −0 Original line number Diff line number Diff line Loading @@ -18,3 +18,20 @@ flag { description: "Enforce PropertyInvalidatedCache.setTestMode() protocol" bug: "360897450" } flag { namespace: "system_performance" name: "pic_isolate_cache_by_uid" is_fixed_read_only: true description: "Ensure that different UIDs use different caches" bug: "373752556" } flag { namespace: "system_performance" name: "pic_isolated_cache_statistics" is_fixed_read_only: true description: "Collects statistics for cache UID isolation strategies" bug: "373752556" } core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +76 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.app; import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID; import static android.app.PropertyInvalidatedCache.NONCE_UNSET; import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH; import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; Loading @@ -30,8 +31,9 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.PropertyInvalidatedCache.Args; import android.annotation.SuppressLint; import android.app.PropertyInvalidatedCache.Args; import android.os.Binder; import com.android.internal.os.ApplicationSharedMemory; import android.platform.test.annotations.IgnoreUnderRavenwood; Loading @@ -58,6 +60,7 @@ import org.junit.Test; */ @SmallTest public class PropertyInvalidatedCacheTests { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); Loading Loading @@ -455,8 +458,9 @@ public class PropertyInvalidatedCacheTests { // Test the Args-style constructor. @Test public void testArgsConstructor() { // Create a cache with a maximum of four entries. TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4), // Create a cache with a maximum of four entries and non-isolated UIDs. TestCache cache = new TestCache(new Args(MODULE_TEST) .maxEntries(4).isolateUids(false).api("init1"), new TestQuery()); cache.invalidateCache(); Loading Loading @@ -570,4 +574,73 @@ public class PropertyInvalidatedCacheTests { // Expected exception. } } // Verify that a cache created with isolatedUids(true) separates out the results. @RequiresFlagsEnabled(FLAG_PIC_ISOLATE_CACHE_BY_UID) @Test public void testIsolatedUids() { TestCache cache = new TestCache(new Args(MODULE_TEST) .maxEntries(4).isolateUids(true).api("testIsolatedUids").testMode(true), new TestQuery()); cache.invalidateCache(); final int uid1 = 1; final int uid2 = 2; long token = Binder.setCallingWorkSourceUid(uid1); try { // Populate the cache for user 1 assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); // Populate the cache for user 2. User 1 values are not reused. Binder.setCallingWorkSourceUid(uid2); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); // Verify that the cache for user 1 is still populated. Binder.setCallingWorkSourceUid(uid1); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } finally { Binder.restoreCallingWorkSource(token); } // Repeat the test with a non-isolated cache. cache = new TestCache(new Args(MODULE_TEST) .maxEntries(4).isolateUids(false).api("testIsolatedUids2").testMode(true), new TestQuery()); cache.invalidateCache(); token = Binder.setCallingWorkSourceUid(uid1); try { // Populate the cache for user 1 assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); // Populate the cache for user 2. User 1 values are reused. Binder.setCallingWorkSourceUid(uid2); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); // Verify that the cache for user 1 is still populated. Binder.setCallingWorkSourceUid(uid1); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); } finally { Binder.restoreCallingWorkSource(token); } } } Loading
core/java/android/app/PropertyInvalidatedCache.java +236 −48 Original line number Diff line number Diff line Loading @@ -32,7 +32,10 @@ import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -92,9 +95,6 @@ public class PropertyInvalidatedCache<Query, Result> { * caching on behalf of other processes. */ public boolean shouldBypassCache(@NonNull Q query) { if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) { return Binder.getCallingUid() != Process.myUid(); } return false; } }; Loading Loading @@ -392,8 +392,213 @@ public class PropertyInvalidatedCache<Query, Result> { } } /** * An array of hash maps, indexed by calling UID. The class behaves a bit like a hash map * except that it uses the calling UID internally. */ private class CacheMap<Query, Result> { // Create a new map for a UID, using the parent's configuration for max size. private LinkedHashMap<Query, Result> createMap() { return new LinkedHashMap<Query, Result>( 2 /* start small */, 0.75f /* default load factor */, true /* LRU access order */) { @GuardedBy("mLock") @Override protected boolean removeEldestEntry(Map.Entry eldest) { final int size = size(); if (size > mHighWaterMark) { mHighWaterMark = size; } if (size > mMaxEntries) { mMissOverflow++; return true; } return false; } }; } // An array of maps, indexed by UID. private final SparseArray<LinkedHashMap<Query, Result>> mCache = new SparseArray<>(); // If true, isolate the hash entries by calling UID. If this is false, allow the cache // entries to be combined in a single hash map. private final boolean mIsolated; // Collect statistics. private final boolean mStatistics; // An array of booleans to indicate if a UID has been involved in a map access. A value // exists for every UID that was ever involved during cache access. This is updated only // if statistics are being collected. private final SparseBooleanArray mUidSeen; // A hash map that ignores the UID. This is used in look-aside fashion just for hit/miss // statistics. This is updated only if statistics are being collected. private final ArraySet<Query> mShadowCache; // Shadow statistics. Only hits and misses need to be recorded. These are updated only // if statistics are being collected. The "SelfHits" records hits when the UID is the // process uid. private int mShadowHits; private int mShadowMisses; private int mShadowSelfHits; // The process UID. private final int mSelfUid; // True in test mode. In test mode, the cache uses Binder.getWorkSource() as the UID. private final boolean mTestMode; /** * Create a CacheMap. UID isolation is enabled if the input parameter is true and if the * isolation feature is enabled. */ CacheMap(boolean isolate, boolean testMode) { mIsolated = Flags.picIsolateCacheByUid() && isolate; mStatistics = Flags.picIsolatedCacheStatistics() && mIsolated; if (mStatistics) { mUidSeen = new SparseBooleanArray(); mShadowCache = new ArraySet<>(); } else { mUidSeen = null; mShadowCache = null; } mSelfUid = Process.myUid(); mTestMode = testMode; } // Return the UID for this cache invocation. If uid isolation is disabled, the value of 0 // is returned, which effectively places all entries in a single hash map. private int callerUid() { if (!mIsolated) { return 0; } else if (mTestMode) { return Binder.getCallingWorkSourceUid(); } else { return Binder.getCallingUid(); } } /** * Lookup an entry in the cache. */ Result get(Query query) { final int uid = callerUid(); // Shadow statistics if (mStatistics) { if (mShadowCache.contains(query)) { mShadowHits++; if (uid == mSelfUid) { mShadowSelfHits++; } } else { mShadowMisses++; } } var map = mCache.get(uid); if (map != null) { return map.get(query); } else { return null; } } /** * Remove an entry from the cache. */ void remove(Query query) { final int uid = callerUid(); if (mStatistics) { mShadowCache.remove(query); } var map = mCache.get(uid); if (map != null) { map.remove(query); } } /** * Record an entry in the cache. */ void put(Query query, Result result) { final int uid = callerUid(); if (mStatistics) { mShadowCache.add(query); mUidSeen.put(uid, true); } var map = mCache.get(uid); if (map == null) { map = createMap(); mCache.put(uid, map); } map.put(query, result); } /** * Return the number of entries in the cache. */ int size() { int total = 0; for (int i = 0; i < mCache.size(); i++) { var map = mCache.valueAt(i); total += map.size(); } return total; } /** * Clear the entries in the cache. Update the shadow statistics. */ void clear() { if (mStatistics) { mShadowCache.clear(); } mCache.clear(); } // Dump basic statistics, if any are collected. Do nothing if statistics are not enabled. void dump(PrintWriter pw) { if (mStatistics) { pw.println(formatSimple(" ShadowHits: %d, ShadowMisses: %d, ShadowSize: %d", mShadowHits, mShadowMisses, mShadowCache.size())); pw.println(formatSimple(" ShadowUids: %d, SelfUid: %d", mUidSeen.size(), mShadowSelfHits)); } } // Dump detailed statistics void dumpDetailed(PrintWriter pw) { for (int i = 0; i < mCache.size(); i++) { int uid = mCache.keyAt(i); var map = mCache.valueAt(i); Set<Map.Entry<Query, Result>> cacheEntries = map.entrySet(); if (cacheEntries.size() == 0) { break; } pw.println(" Contents:"); pw.println(formatSimple(" Uid: %d\n", uid)); for (Map.Entry<Query, Result> entry : cacheEntries) { String key = Objects.toString(entry.getKey()); String value = Objects.toString(entry.getValue()); pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value)); } } } } @GuardedBy("mLock") private final LinkedHashMap<Query, Result> mCache; private final CacheMap<Query, Result> mCache; /** * The nonce handler for this cache. Loading Loading @@ -895,7 +1100,8 @@ public class PropertyInvalidatedCache<Query, Result> { * is allowed to be null in the record constructor to facility reuse of Args instances. * @hide */ public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) { public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries, boolean mIsolateUids, boolean mTestMode) { // Validation: the module must be one of the known module strings and the maxEntries must // be positive. Loading @@ -909,15 +1115,28 @@ public class PropertyInvalidatedCache<Query, Result> { // which is not legal, but there is no reasonable default. Clients must call the api // method to set the field properly. public Args(@NonNull String module) { this(module, /* api */ null, /* maxEntries */ 32); this(module, null, // api 32, // maxEntries true, // isolateUids false // testMode ); } public Args api(@NonNull String api) { return new Args(mModule, api, mMaxEntries); return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode); } public Args maxEntries(int val) { return new Args(mModule, mApi, val); return new Args(mModule, mApi, val, mIsolateUids, mTestMode); } public Args isolateUids(boolean val) { return new Args(mModule, mApi, mMaxEntries, val, mTestMode); } public Args testMode(boolean val) { return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val); } } Loading @@ -936,7 +1155,7 @@ public class PropertyInvalidatedCache<Query, Result> { mCacheName = cacheName; mNonce = getNonceHandler(mPropertyName); mMaxEntries = args.mMaxEntries; mCache = createMap(); mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode); mComputer = (computer != null) ? computer : new DefaultComputer<>(this); registerCache(); } Loading Loading @@ -1006,28 +1225,6 @@ public class PropertyInvalidatedCache<Query, Result> { this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer); } // Create a map. This should be called only from the constructor. private LinkedHashMap<Query, Result> createMap() { return new LinkedHashMap<Query, Result>( 2 /* start small */, 0.75f /* default load factor */, true /* LRU access order */) { @GuardedBy("mLock") @Override protected boolean removeEldestEntry(Map.Entry eldest) { final int size = size(); if (size > mHighWaterMark) { mHighWaterMark = size; } if (size > mMaxEntries) { mMissOverflow++; return true; } return false; } }; } /** * Register the map in the global list. If the cache is disabled globally, disable it * now. This method is only ever called from the constructor, which means no other thread has Loading Loading @@ -1778,8 +1975,8 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(formatSimple(" Cache Name: %s", cacheName())); pw.println(formatSimple(" Property: %s", mPropertyName)); pw.println(formatSimple( " Hits: %d, Misses: %d, Skips: %d, Clears: %d", mHits, mMisses, getSkipsLocked(), mClears)); " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d", mHits, mMisses, getSkipsLocked(), mClears, mCache.size())); // Print all the skip reasons. pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]); Loading @@ -1794,25 +1991,16 @@ public class PropertyInvalidatedCache<Query, Result> { pw.println(formatSimple( " Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d", mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow)); mCache.dump(pw); pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true")); // No specific cache was requested. This is the default, and no details // should be dumped. if (!detailed) { return; } Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet(); if (cacheEntries.size() == 0) { return; // Dump the contents of the cache. if (detailed) { mCache.dumpDetailed(pw); } pw.println(" Contents:"); for (Map.Entry<Query, Result> entry : cacheEntries) { String key = Objects.toString(entry.getKey()); String value = Objects.toString(entry.getValue()); pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value)); } // Separator between caches. pw.println(""); } } Loading
core/java/android/app/performance.aconfig +17 −0 Original line number Diff line number Diff line Loading @@ -18,3 +18,20 @@ flag { description: "Enforce PropertyInvalidatedCache.setTestMode() protocol" bug: "360897450" } flag { namespace: "system_performance" name: "pic_isolate_cache_by_uid" is_fixed_read_only: true description: "Ensure that different UIDs use different caches" bug: "373752556" } flag { namespace: "system_performance" name: "pic_isolated_cache_statistics" is_fixed_read_only: true description: "Collects statistics for cache UID isolation strategies" bug: "373752556" }
core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +76 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.app; import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID; import static android.app.PropertyInvalidatedCache.NONCE_UNSET; import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH; import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; Loading @@ -30,8 +31,9 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.PropertyInvalidatedCache.Args; import android.annotation.SuppressLint; import android.app.PropertyInvalidatedCache.Args; import android.os.Binder; import com.android.internal.os.ApplicationSharedMemory; import android.platform.test.annotations.IgnoreUnderRavenwood; Loading @@ -58,6 +60,7 @@ import org.junit.Test; */ @SmallTest public class PropertyInvalidatedCacheTests { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); Loading Loading @@ -455,8 +458,9 @@ public class PropertyInvalidatedCacheTests { // Test the Args-style constructor. @Test public void testArgsConstructor() { // Create a cache with a maximum of four entries. TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4), // Create a cache with a maximum of four entries and non-isolated UIDs. TestCache cache = new TestCache(new Args(MODULE_TEST) .maxEntries(4).isolateUids(false).api("init1"), new TestQuery()); cache.invalidateCache(); Loading Loading @@ -570,4 +574,73 @@ public class PropertyInvalidatedCacheTests { // Expected exception. } } // Verify that a cache created with isolatedUids(true) separates out the results. @RequiresFlagsEnabled(FLAG_PIC_ISOLATE_CACHE_BY_UID) @Test public void testIsolatedUids() { TestCache cache = new TestCache(new Args(MODULE_TEST) .maxEntries(4).isolateUids(true).api("testIsolatedUids").testMode(true), new TestQuery()); cache.invalidateCache(); final int uid1 = 1; final int uid2 = 2; long token = Binder.setCallingWorkSourceUid(uid1); try { // Populate the cache for user 1 assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); // Populate the cache for user 2. User 1 values are not reused. Binder.setCallingWorkSourceUid(uid2); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); // Verify that the cache for user 1 is still populated. Binder.setCallingWorkSourceUid(uid1); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } finally { Binder.restoreCallingWorkSource(token); } // Repeat the test with a non-isolated cache. cache = new TestCache(new Args(MODULE_TEST) .maxEntries(4).isolateUids(false).api("testIsolatedUids2").testMode(true), new TestQuery()); cache.invalidateCache(); token = Binder.setCallingWorkSourceUid(uid1); try { // Populate the cache for user 1 assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); // Populate the cache for user 2. User 1 values are reused. Binder.setCallingWorkSourceUid(uid2); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); // Verify that the cache for user 1 is still populated. Binder.setCallingWorkSourceUid(uid1); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); } finally { Binder.restoreCallingWorkSource(token); } } }