Merge "Export PropertyInvalidatedCache SystemApis"

This commit is contained in:
Lee Shombert
2022-02-01 20:29:22 +00:00
committed by Android (Google) Code Review
4 changed files with 432 additions and 99 deletions

View File

@@ -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);
}

View File

@@ -355,6 +355,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);

View File

@@ -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()) {

View File

@@ -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,45 +175,29 @@ 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 static class TestCache extends PropertyInvalidatedCache<Integer, String> {
TestCache() {
this(CACHE_PROPERTY);
}
TestCache(String key) {
super(4, key);
setTestMode(true);
testPropertyName(key);
}
private int mRecomputeCount = 0;
@Override
public String recompute(Integer qv) {
public String apply(Integer qv) {
mRecomputeCount += 1;
return "foo" + qv.toString();
}
@@ -222,15 +205,40 @@ public class PropertyInvalidatedCacheTests {
int getRecomputeCount() {
return mRecomputeCount;
}
}
private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
private final TestQuery mQuery;
TestCache() {
this(MODULE, API);
}
TestCache(int module, String api) {
this(module, api, new TestQuery());
setTestMode(true);
testPropertyName();
}
TestCache(int module, String api, TestQuery query) {
super(4, module, api, api, query);
mQuery = query;
setTestMode(true);
testPropertyName();
}
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");
}
}