diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index df61a968ffc0f..6cde547e6571e 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 { + ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler); + 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 { + 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 15148a93cbe72..c0d0c972066b7 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 { + ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler); + 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 { + 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 ec8d989c61d4c..715de14345f77 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; *
  * 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  The class used to index cache entries: must be hashable and comparable
  * @param  The class holding cache entries; use a boxed primitive if possible
- *
- * {@hide}
+ * @hide
  */
-public abstract class PropertyInvalidatedCache {
+@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+@TestApi
+public class PropertyInvalidatedCache {
+    /**
+     * This is a configuration class that customizes a cache instance.
+     * @hide
+     */
+    @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    public static abstract class QueryHandler {
+        /**
+         * 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 ..,
+     * 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
+     * ..  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 {
      */
     private final String mCacheName;
 
+    /**
+     * The function that computes a Result, given a Query.  This function is called on a
+     * cache miss.
+     */
+    private QueryHandler mComputer;
+
+    /**
+     * A default function that delegates to the deprecated recompute() method.
+     */
+    private static class DefaultComputer extends QueryHandler {
+        final PropertyInvalidatedCache mCache;
+        DefaultComputer(PropertyInvalidatedCache cache) {
+            mCache = cache;
+        }
+        public Result apply(Query query) {
+            return mCache.recompute(query);
+        }
+    }
+
     @GuardedBy("mLock")
     private final LinkedHashMap mCache;
 
@@ -359,8 +501,13 @@ public abstract class PropertyInvalidatedCache {
      * 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 {
     /**
      * 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(
+        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 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 createMap() {
+        return new LinkedHashMap(
             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 {
     /**
      * 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 {
      * 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 {
 
     /**
      * 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 {
      * 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 {
      * 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 {
      * 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 {
      * 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 {
      * 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 {
      * 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 {
     /**
      * 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 {
      * 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 {
      * 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 {
      * 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 {
      * 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 {
      * 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 {
     /**
      * 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 {
     }
 
     /**
-     * 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 activeCaches;
@@ -1174,6 +1428,7 @@ public abstract class PropertyInvalidatedCache {
 
     /**
      * 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 d3e8bb0ec3171..5338d046596ac 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 {
+        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 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 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 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 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 {
 
-    private static class TestCache extends PropertyInvalidatedCache {
-        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 {
+        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");
+    }
 }