Merge "Export PropertyInvalidatedCache SystemApis"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user