diff options
| -rw-r--r-- | core/api/module-lib-current.txt | 15 | ||||
| -rw-r--r-- | core/api/test-current.txt | 26 | ||||
| -rw-r--r-- | core/java/android/app/PropertyInvalidatedCache.java | 327 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java | 161 |
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<Integer, Birthday> mBirthdayQuery = + * new PropertyInvalidatedCache.QueryHandler<Integer, Birthday>() { + * {@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<Integer, Birthday%> mBirthdayCache = new - * PropertyInvalidatedCache<Integer, Birthday%>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) { - * {@literal @}Override - * protected Birthday recompute(Integer userId) { - * return GetService("birthdayd").getUserBirthday(userId); - * } - * }; + * PropertyInvalidatedCache<Integer, Birthday%>( + * 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"); + } } |