summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/module-lib-current.txt15
-rw-r--r--core/api/test-current.txt26
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java327
-rw-r--r--core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java161
4 files changed, 431 insertions, 98 deletions
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index df61a968ffc0..6cde547e6571 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -54,6 +54,21 @@ package android.app {
method public void onCanceled(@NonNull android.app.PendingIntent);
}
+ public class PropertyInvalidatedCache<Query, Result> {
+ ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+ method public final void disableForCurrentProcess();
+ method public final void invalidateCache();
+ method public static void invalidateCache(int, @NonNull String);
+ method @Nullable public final Result query(@NonNull Query);
+ field public static final int MODULE_BLUETOOTH = 2; // 0x2
+ }
+
+ public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+ ctor public PropertyInvalidatedCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
+ }
+
public class StatusBarManager {
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 15148a93cbe7..c0d0c972066b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -356,6 +356,32 @@ package android.app {
ctor public PictureInPictureUiState(boolean);
}
+ public class PropertyInvalidatedCache<Query, Result> {
+ ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
+ method @NonNull public static String createPropertyName(int, @NonNull String);
+ method public final void disableForCurrentProcess();
+ method public static void disableForTestMode();
+ method public final void disableInstance();
+ method public final void disableSystemWide();
+ method public final void forgetDisableLocal();
+ method public boolean getDisabledState();
+ method public final void invalidateCache();
+ method public static void invalidateCache(int, @NonNull String);
+ method public final boolean isDisabled();
+ method @Nullable public final Result query(@NonNull Query);
+ method public static void setTestMode(boolean);
+ method public void testPropertyName();
+ field public static final int MODULE_BLUETOOTH = 2; // 0x2
+ field public static final int MODULE_SYSTEM = 1; // 0x1
+ field public static final int MODULE_TEST = 0; // 0x0
+ }
+
+ public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
+ ctor public PropertyInvalidatedCache.QueryHandler();
+ method @Nullable public abstract R apply(@NonNull Q);
+ method public boolean shouldBypassCache(@NonNull Q);
+ }
+
public class StatusBarManager {
method public void cancelRequestAddTile(@NonNull String);
method public void clickNotification(@Nullable String, int, int, boolean);
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index ec8d989c61d4..715de14345f7 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -16,7 +16,11 @@
package android.app;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -27,9 +31,10 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastPrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -104,17 +109,21 @@ import java.util.concurrent.atomic.AtomicLong;
* <pre>
* public class ActivityThread {
* ...
+ * private final PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt; mBirthdayQuery =
+ * new PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt;() {
+ * {@literal @}Override
+ * public Birthday apply(Integer) {
+ * return GetService("birthdayd").getUserBirthday(userId);
+ * }
+ * };
* private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache
* private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
* private final PropertyInvalidatedCache&lt;Integer, Birthday%&gt; mBirthdayCache = new
- * PropertyInvalidatedCache&lt;Integer, Birthday%&gt;(BDAY_CACHE_MAX, BDAY_CACHE_KEY) {
- * {@literal @}Override
- * protected Birthday recompute(Integer userId) {
- * return GetService("birthdayd").getUserBirthday(userId);
- * }
- * };
+ * PropertyInvalidatedCache&lt;Integer, Birthday%&gt;(
+ * BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery);
+ *
* public void disableUserBirthdayCache() {
- * mBirthdayCache.disableLocal();
+ * mBirthdayCache.disableForCurrentProcess();
* }
* public void invalidateUserBirthdayCache() {
* mBirthdayCache.invalidateCache();
@@ -221,10 +230,124 @@ import java.util.concurrent.atomic.AtomicLong;
*
* @param <Query> The class used to index cache entries: must be hashable and comparable
* @param <Result> The class holding cache entries; use a boxed primitive if possible
- *
- * {@hide}
+ * @hide
*/
-public abstract class PropertyInvalidatedCache<Query, Result> {
+@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public class PropertyInvalidatedCache<Query, Result> {
+ /**
+ * This is a configuration class that customizes a cache instance.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static abstract class QueryHandler<Q,R> {
+ /**
+ * Compute a result given a query. The semantics are those of Functor.
+ */
+ public abstract @Nullable R apply(@NonNull Q query);
+
+ /**
+ * Return true if a query should not use the cache. The default implementation
+ * always uses the cache.
+ */
+ public boolean shouldBypassCache(@NonNull Q query) {
+ return false;
+ }
+ };
+
+ /**
+ * The system properties used by caches should be of the form <prefix>.<module>.<api>,
+ * where the prefix is "cache_key", the module is one of the constants below, and the
+ * api is any string. The ability to write the property (which happens during
+ * invalidation) depends on SELinux rules; these rules are defined against
+ * <prefix>.<module>. Therefore, the module chosen for a cache property must match
+ * the permissions granted to the processes that contain the corresponding caches.
+ * @hide
+ */
+ @IntDef(prefix = { "MODULE_" }, value = {
+ MODULE_TEST,
+ MODULE_SYSTEM,
+ MODULE_BLUETOOTH
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Module {}
+
+ /**
+ * The module used for unit tests and cts tests. It is expected that no process in
+ * the system has permissions to write properties with this module.
+ * @hide
+ */
+ @TestApi
+ public static final int MODULE_TEST = 0;
+
+ /**
+ * The module used for system server/framework caches. This is not visible outside
+ * the system processes.
+ * @hide
+ */
+ @TestApi
+ public static final int MODULE_SYSTEM = 1;
+
+ /**
+ * The module used for bluetooth caches.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static final int MODULE_BLUETOOTH = 2;
+
+ // A static array mapping module constants to strings.
+ private final static String[] sModuleNames =
+ { "test", "system_server", "bluetooth" };
+
+ /**
+ * Construct a system property that matches the rules described above. The module is
+ * one of the permitted values above. The API is a string that is a legal Java simple
+ * identifier. The api is modified to conform to the system property style guide by
+ * replacing every upper case letter with an underscore and the lower case equivalent.
+ * There is no requirement that the apiName be the name of an actual API.
+ *
+ * Be aware that SystemProperties has a maximum length which is private to the
+ * implementation. The current maximum is 92 characters. If this method creates a
+ * property name that is too long, SystemProperties.set() will fail without a good
+ * error message.
+ * @hide
+ */
+ @TestApi
+ public static @NonNull String createPropertyName(@Module int module, @NonNull String apiName) {
+ char[] api = apiName.toCharArray();
+ int upper = 0;
+ for (int i = 0; i < api.length; i++) {
+ if (Character.isUpperCase(api[i])) {
+ upper++;
+ }
+ }
+ char[] suffix = new char[api.length + upper];
+ int j = 0;
+ for (int i = 0; i < api.length; i++) {
+ if (Character.isJavaIdentifierPart(api[i])) {
+ if (Character.isUpperCase(api[i])) {
+ suffix[j++] = '_';
+ suffix[j++] = Character.toLowerCase(api[i]);
+ } else {
+ suffix[j++] = api[i];
+ }
+ } else {
+ throw new IllegalArgumentException("invalid api name");
+ }
+ }
+
+ String moduleName = null;
+ try {
+ moduleName = sModuleNames[module];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("invalid module " + module);
+ }
+
+ return "cache_key." + moduleName + "." + new String(suffix);
+ }
+
/**
* Reserved nonce values. Use isReservedNonce() to test for a reserved value. Note
* that all values cause the cache to be skipped.
@@ -335,6 +458,25 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
*/
private final String mCacheName;
+ /**
+ * The function that computes a Result, given a Query. This function is called on a
+ * cache miss.
+ */
+ private QueryHandler<Query, Result> mComputer;
+
+ /**
+ * A default function that delegates to the deprecated recompute() method.
+ */
+ private static class DefaultComputer<Query, Result> extends QueryHandler<Query, Result> {
+ final PropertyInvalidatedCache<Query, Result> mCache;
+ DefaultComputer(PropertyInvalidatedCache<Query, Result> cache) {
+ mCache = cache;
+ }
+ public Result apply(Query query) {
+ return mCache.recompute(query);
+ }
+ }
+
@GuardedBy("mLock")
private final LinkedHashMap<Query, Result> mCache;
@@ -359,8 +501,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* property name. New clients should prefer the constructor that takes an explicit
* cache name.
*
+ * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+ * constructor.
+ *
* @param maxEntries Maximum number of entries to cache; LRU discard
* @param propertyName Name of the system property holding the cache invalidation nonce.
+ *
+ * @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
this(maxEntries, propertyName, propertyName);
@@ -369,32 +516,73 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
/**
* Make a new property invalidated cache.
*
+ * TODO(216112648): deprecate this as a public interface, in favor of the four-argument
+ * constructor.
+ *
* @param maxEntries Maximum number of entries to cache; LRU discard
* @param propertyName Name of the system property holding the cache invalidation nonce
* @param cacheName Name of this cache in debug and dumpsys
+ * @hide
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
@NonNull String cacheName) {
mPropertyName = propertyName;
mCacheName = cacheName;
mMaxEntries = maxEntries;
- mCache = new LinkedHashMap<Query, Result>(
+ mComputer = new DefaultComputer<>(this);
+ mCache = createMap();
+ registerCache();
+ }
+
+ /**
+ * Make a new property invalidated cache. The key is computed from the module and api
+ * parameters.
+ *
+ * @param maxEntries Maximum number of entries to cache; LRU discard
+ * @param module The module under which the cache key should be placed.
+ * @param api The api this cache front-ends. The api must be a Java identifier but
+ * need not be an actual api.
+ * @param cacheName Name of this cache in debug and dumpsys
+ * @param computer The code to compute values that are not in the cache.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public PropertyInvalidatedCache(int maxEntries, @Module int module, @NonNull String api,
+ @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
+ mPropertyName = createPropertyName(module, api);
+ mCacheName = cacheName;
+ mMaxEntries = maxEntries;
+ mComputer = computer;
+ mCache = createMap();
+ registerCache();
+ }
+
+ // 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 > maxEntries) {
+ if (size > mMaxEntries) {
mMissOverflow++;
return true;
}
return false;
}
- };
+ };
+ }
+
+ // Register the map in the global list. If the cache is disabled globally, disable it
+ // now.
+ private void registerCache() {
synchronized (sCorkLock) {
sCaches.put(this, null);
if (sDisabledKeys.contains(mCacheName)) {
@@ -418,8 +606,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
/**
* Enable or disable testing. The testing property map is cleared every time this
* method is called.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public static void setTestMode(boolean mode) {
sTesting = mode;
synchronized (sTestingPropertyMap) {
@@ -431,13 +620,22 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* Enable testing the specific cache key. Only keys in the map are subject to testing.
* There is no method to stop testing a property name. Just disable the test mode.
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public static void testPropertyName(@NonNull String name) {
+ private static void testPropertyName(@NonNull String name) {
synchronized (sTestingPropertyMap) {
sTestingPropertyMap.put(name, (long) NONCE_UNSET);
}
}
+ /**
+ * Enable testing the specific cache key. Only keys in the map are subject to testing.
+ * There is no method to stop testing a property name. Just disable the test mode.
+ * @hide
+ */
+ @TestApi
+ public void testPropertyName() {
+ testPropertyName(mPropertyName);
+ }
+
// Read the system property associated with the current cache. This method uses the
// handle for faster reading.
private long getCurrentNonce() {
@@ -490,6 +688,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
/**
* Forget all cached values.
+ * TODO(216112648) remove this as a public API. Clients should invalidate caches, not clear
+ * them.
+ * @hide
*/
public final void clear() {
synchronized (mLock) {
@@ -505,22 +706,29 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* Fetch a result from scratch in case it's not in the cache at all. Called unlocked: may
* block. If this function returns null, the result of the cache query is null. There is no
* "negative cache" in the query: we don't cache null results at all.
+ * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+ * QueryHandler.
+ * @hide
*/
- public abstract @NonNull Result recompute(@NonNull Query query);
+ public Result recompute(@NonNull Query query) {
+ return mComputer.apply(query);
+ }
/**
* Return true if the query should bypass the cache. The default behavior is to
* always use the cache but the method can be overridden for a specific class.
+ * TODO(216112648): deprecate this as a public interface, in favor of an instance of
+ * QueryHandler.
+ * @hide
*/
public boolean bypass(@NonNull Query query) {
- return false;
+ return mComputer.shouldBypassCache(query);
}
/**
- * Determines if a pair of responses are considered equal. Used to determine whether a
- * cache is inadvertently returning stale results when VERIFY is set to true. Some
- * existing clients override this method, but it is now deprecated in favor of a valid
- * equals() method on the Result class.
+ * Determines if a pair of responses are considered equal. Used to determine whether
+ * a cache is inadvertently returning stale results when VERIFY is set to true.
+ * @hide
*/
public boolean resultEquals(Result cachedResult, Result fetchedResult) {
// If a service crashes and returns a null result, the cached value remains valid.
@@ -541,6 +749,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* the meantime (if the nonce has changed in the meantime, we drop the cache and try the
* whole query again), or 3) null, which causes the old value to be removed from the cache
* and null to be returned as the result of the cache query.
+ * @hide
*/
protected Result refresh(Result oldResult, Query query) {
return oldResult;
@@ -551,7 +760,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* testing. To disable a cache in normal code, use disableLocal().
* @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public final void disableInstance() {
synchronized (mLock) {
mDisabled = true;
@@ -580,9 +789,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* disabled remain disabled (the "disabled" setting is sticky). However, new caches
* with this name will not be disabled. It is not an error if the cache name is not
* found in the list of disabled caches.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public final void clearDisableLocal() {
+ @TestApi
+ public final void forgetDisableLocal() {
synchronized (sCorkLock) {
sDisabledKeys.remove(mCacheName);
}
@@ -592,25 +802,43 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* Disable this cache in the current process, and all other caches that use the same
* name. This does not affect caches that have a different name but use the same
* property.
+ * TODO(216112648) Remove this in favor of disableForCurrentProcess().
+ * @hide
*/
public final void disableLocal() {
+ disableForCurrentProcess();
+ }
+
+ /**
+ * Disable this cache in the current process, and all other caches that use the same
+ * name. This does not affect caches that have a different name but use the same
+ * property.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final void disableForCurrentProcess() {
disableLocal(mCacheName);
}
/**
- * Return whether the cache is disabled in this process.
+ * Return whether a cache instance is disabled.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public final boolean isDisabledLocal() {
+ @TestApi
+ public final boolean isDisabled() {
return mDisabled || !sEnabled;
}
/**
* Get a value from the cache or recompute it.
+ * @hide
*/
- public @NonNull Result query(@NonNull Query query) {
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public final @Nullable Result query(@NonNull Query query) {
// Let access to mDisabled race: it's atomic anyway.
- long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
+ long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
if (bypass(query)) {
currentNonce = NONCE_BYPASS;
}
@@ -724,8 +952,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* When multiple caches share a single property value, using an instance method on one of
* the cache objects to invalidate all of the cache objects becomes confusing and you should
* just use the static version of this function.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public final void disableSystemWide() {
disableSystemWide(mPropertyName);
}
@@ -746,16 +975,33 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
/**
* Non-static convenience version of invalidateCache() for situations in which only a single
* PropertyInvalidatedCache is keyed on a particular property value.
+ * @hide
*/
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
public final void invalidateCache() {
invalidateCache(mPropertyName);
}
/**
+ * Invalidate caches in all processes that are keyed for the module and api.
+ * @hide
+ */
+ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+ @TestApi
+ public static void invalidateCache(@Module int module, @NonNull String api) {
+ invalidateCache(createPropertyName(module, api));
+ }
+
+ /**
* Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
* {@var name}. This function is synchronous: caches are invalidated upon return.
*
+ * TODO(216112648) make this method private in favor of the two-argument (module, api)
+ * override.
+ *
* @param name Name of the cache-key property to invalidate
+ * @hide
*/
public static void invalidateCache(@NonNull String name) {
if (!sEnabled) {
@@ -824,6 +1070,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* corkInvalidations() and uncorkInvalidations() must be called in pairs.
*
* @param name Name of the cache-key property to cork
+ * @hide
*/
public static void corkInvalidations(@NonNull String name) {
if (!sEnabled) {
@@ -871,6 +1118,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* transitioning it to normal operation (unless explicitly disabled system-wide).
*
* @param name Name of the cache-key property to uncork
+ * @hide
*/
public static void uncorkInvalidations(@NonNull String name) {
if (!sEnabled) {
@@ -916,6 +1164,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* The auto-cork delay is configurable but it should not be too long. The purpose of
* the delay is to minimize the number of times a server writes to the system property
* when invalidating the cache. One write every 50ms does not hurt system performance.
+ * @hide
*/
public static final class AutoCorker {
public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
@@ -1043,6 +1292,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* Return the query as a string, to be used in debug messages. New clients should not
* override this, but should instead add the necessary toString() method to the Query
* class.
+ * TODO(216112648) add a method in the QueryHandler and deprecate this API.
+ * @hide
*/
protected @NonNull String queryToString(@NonNull Query query) {
return Objects.toString(query);
@@ -1054,8 +1305,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
* process does not have privileges to write SystemProperties. Once disabled it is not
* possible to re-enable caching in the current process. If a client wants to
* temporarily disable caching, use the corking mechanism.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public static void disableForTestMode() {
Log.d(TAG, "disabling all caches in the process");
sEnabled = false;
@@ -1064,10 +1316,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
/**
* Report the disabled status of this cache instance. The return value does not
* reflect status of the property key.
+ * @hide
*/
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @TestApi
public boolean getDisabledState() {
- return isDisabledLocal();
+ return isDisabled();
}
/**
@@ -1133,7 +1386,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
}
/**
- * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor.
+ * Dumps contents of every cache in the process to the provided ParcelFileDescriptor.
+ * @hide
*/
public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
ArrayList<PropertyInvalidatedCache> activeCaches;
@@ -1174,6 +1428,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
/**
* Trim memory by clearing all the caches.
+ * @hide
*/
public static void onTrimMemory() {
for (PropertyInvalidatedCache pic : getActiveCaches()) {
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index d3e8bb0ec317..5338d046596a 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -37,9 +37,9 @@ import org.junit.Test;
@SmallTest
public class PropertyInvalidatedCacheTests {
- // This property is never set. The test process does not have permission to set any
- // properties.
- static final String CACHE_PROPERTY = "cache_key.cache_test_a";
+ // Configuration for creating caches
+ private static final int MODULE = PropertyInvalidatedCache.MODULE_TEST;
+ private static final String API = "testApi";
// This class is a proxy for binder calls. It contains a counter that increments
// every time the class is queried.
@@ -64,6 +64,25 @@ public class PropertyInvalidatedCacheTests {
}
}
+ // The functions for querying the server.
+ private static class ServerQuery
+ extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> {
+ private final ServerProxy mServer;
+
+ ServerQuery(ServerProxy server) {
+ mServer = server;
+ }
+
+ @Override
+ public Boolean apply(Integer x) {
+ return mServer.query(x);
+ }
+ @Override
+ public boolean shouldBypassCache(Integer x) {
+ return x % 13 == 0;
+ }
+ }
+
// Clear the test mode after every test, in case this process is used for other
// tests. This also resets the test property map.
@After
@@ -82,19 +101,11 @@ public class PropertyInvalidatedCacheTests {
// Create a cache that uses simple arithmetic to computer its values.
PropertyInvalidatedCache<Integer, Boolean> testCache =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- @Override
- public boolean bypass(Integer x) {
- return x % 13 == 0;
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache.setTestMode(true);
- PropertyInvalidatedCache.testPropertyName(CACHE_PROPERTY);
+ testCache.testPropertyName();
tester.verify(0);
assertEquals(tester.value(3), testCache.query(3));
@@ -136,26 +147,14 @@ public class PropertyInvalidatedCacheTests {
// Three caches, all using the same system property but one uses a different name.
PropertyInvalidatedCache<Integer, Boolean> cache1 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache<Integer, Boolean> cache2 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
PropertyInvalidatedCache<Integer, Boolean> cache3 =
- new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ new PropertyInvalidatedCache<>(4, MODULE, API, "cache3",
+ new ServerQuery(tester));
// Caches are enabled upon creation.
assertEquals(false, cache1.getDisabledState());
@@ -176,61 +175,70 @@ public class PropertyInvalidatedCacheTests {
assertEquals(false, cache3.getDisabledState());
// Create a new cache1. Verify that the new instance is disabled.
- cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
assertEquals(true, cache1.getDisabledState());
// Remove the record of caches being locally disabled. This is a clean-up step.
- cache1.clearDisableLocal();
+ cache1.forgetDisableLocal();
assertEquals(true, cache1.getDisabledState());
assertEquals(true, cache2.getDisabledState());
assertEquals(false, cache3.getDisabledState());
// Create a new cache1. Verify that the new instance is not disabled.
- cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) {
- @Override
- public Boolean recompute(Integer x) {
- return tester.query(x);
- }
- };
+ cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1",
+ new ServerQuery(tester));
assertEquals(false, cache1.getDisabledState());
}
- private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
+ private static class TestQuery
+ extends PropertyInvalidatedCache.QueryHandler<Integer, String> {
+
+ private int mRecomputeCount = 0;
+
+ @Override
+ public String apply(Integer qv) {
+ mRecomputeCount += 1;
+ return "foo" + qv.toString();
+ }
+
+ int getRecomputeCount() {
+ return mRecomputeCount;
+ }
+ }
private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
+ private final TestQuery mQuery;
+
TestCache() {
- this(CACHE_PROPERTY);
+ this(MODULE, API);
}
- TestCache(String key) {
- super(4, key);
+ TestCache(int module, String api) {
+ this(module, api, new TestQuery());
setTestMode(true);
- testPropertyName(key);
+ testPropertyName();
}
- @Override
- public String recompute(Integer qv) {
- mRecomputeCount += 1;
- return "foo" + qv.toString();
+ TestCache(int module, String api, TestQuery query) {
+ super(4, module, api, api, query);
+ mQuery = query;
+ setTestMode(true);
+ testPropertyName();
}
- int getRecomputeCount() {
- return mRecomputeCount;
+ public int getRecomputeCount() {
+ return mQuery.getRecomputeCount();
}
- private int mRecomputeCount = 0;
+
}
@Test
public void testCacheRecompute() {
TestCache cache = new TestCache();
cache.invalidateCache();
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
assertEquals("foo5", cache.query(5));
@@ -241,6 +249,11 @@ public class PropertyInvalidatedCacheTests {
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(3, cache.getRecomputeCount());
+ // Invalidate the cache with a direct call to the property.
+ PropertyInvalidatedCache.invalidateCache(MODULE, API);
+ assertEquals("foo5", cache.query(5));
+ assertEquals("foo5", cache.query(5));
+ assertEquals(4, cache.getRecomputeCount());
}
@Test
@@ -257,7 +270,8 @@ public class PropertyInvalidatedCacheTests {
@Test
public void testCachePropertyUnset() {
- TestCache cache = new TestCache(UNSET_KEY);
+ final String UNSET_API = "otherApi";
+ TestCache cache = new TestCache(MODULE, UNSET_API);
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(2, cache.getRecomputeCount());
@@ -327,17 +341,40 @@ public class PropertyInvalidatedCacheTests {
@Test
public void testLocalProcessDisable() {
TestCache cache = new TestCache();
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
cache.invalidateCache();
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
assertEquals("foo5", cache.query(5));
assertEquals(1, cache.getRecomputeCount());
- assertEquals(cache.isDisabledLocal(), false);
+ assertEquals(cache.isDisabled(), false);
cache.disableLocal();
- assertEquals(cache.isDisabledLocal(), true);
+ assertEquals(cache.isDisabled(), true);
assertEquals("foo5", cache.query(5));
assertEquals("foo5", cache.query(5));
assertEquals(3, cache.getRecomputeCount());
}
+
+ @Test
+ public void testPropertyNames() {
+ String n1;
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+ assertEquals(n1, "cache_key.system_server.get_package_info");
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+ assertEquals(n1, "cache_key.system_server.get_package_info");
+ try {
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_SYSTEM - 1, "get package_info");
+ // n1 is an invalid api name.
+ assertEquals(false, true);
+ } catch (IllegalArgumentException e) {
+ // An exception is expected here.
+ }
+
+ n1 = PropertyInvalidatedCache.createPropertyName(
+ PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+ assertEquals(n1, "cache_key.bluetooth.get_state");
+ }
}