/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.os; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.PropertyInvalidatedCache; import android.app.PropertyInvalidatedCache.Args; import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; 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; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicLong; /** * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing, * but doesn't hold a lock across data fetches on query misses. * * Clients should be aware of the following commonly-seen issues: * * * The intended use case is caching frequently-read, seldom-changed information normally retrieved * across interprocess communication. Imagine that you've written a user birthday information * daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface over * binder. That binder interface looks something like this: * *
 * parcelable Birthday {
 *   int month;
 *   int day;
 * }
 * interface IUserBirthdayService {
 *   Birthday getUserBirthday(int userId);
 * }
 * 
* * Suppose the service implementation itself looks like this... * *
 * public class UserBirthdayServiceImpl implements IUserBirthdayService {
 *   private final HashMap<Integer, Birthday%> mUidToBirthday;
 *   {@literal @}Override
 *   public synchronized Birthday getUserBirthday(int userId) {
 *     return mUidToBirthday.get(userId);
 *   }
 *   private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) {
 *     mUidToBirthday.clear();
 *     mUidToBirthday.putAll(uidToBirthday);
 *   }
 * }
 * 
* * ... and we have a client in frameworks (loaded into every app process) that looks like this: * *
 * public class ActivityThread {
 *   ...
 *   public Birthday getUserBirthday(int userId) {
 *     return GetService("birthdayd").getUserBirthday(userId);
 *   }
 *   ...
 * }
 * 
* * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call to * the birthdayd process and consult its database of birthdays. If we query user birthdays * frequently, we do a lot of work that we don't have to do, since user birthdays change * infrequently. * * IpcDataCache is part of a pattern for optimizing this kind of information-querying code. Using * {@code IpcDataCache}, you'd write the client this way: * *
 * public class ActivityThread {
 *   ...
 *   private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
 *       new IpcDataCache.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_API = "getUserBirthday";
 *   private final IpcDataCache<Integer, Birthday%> mBirthdayCache = new
 *     IpcDataCache<Integer, Birthday%>(
 *             BDAY_CACHE_MAX, MODULE_SYSTEM, BDAY_API,  BDAY_API, mBirthdayQuery);
 *
 *   public void disableUserBirthdayCache() {
 *     mBirthdayCache.disableForCurrentProcess();
 *   }
 *   public void invalidateUserBirthdayCache() {
 *     mBirthdayCache.invalidateCache();
 *   }
 *   public Birthday getUserBirthday(int userId) {
 *     return mBirthdayCache.query(userId);
 *   }
 *   ...
 * }
 * 
* * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday * for the first time; on subsequent queries, we return the already-known Birthday object. * * The second parameter to the IpcDataCache constructor is a string that identifies the "module" * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any * string is permitted. The third parameters is the name of the API being cached; this, too, can * any value. The fourth is the name of the cache. The cache is usually named after th API. * Some things you must know about the three strings: * * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: * *
 * public class UserBirthdayServiceImpl {
 *   ...
 *   public UserBirthdayServiceImpl() {
 *     ...
 *     ActivityThread.currentActivityThread().disableUserBirthdayCache();
 *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
 *   }
 *
 *   private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) {
 *     mUidToBirthday.clear();
 *     mUidToBirthday.putAll(uidToBirthday);
 *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
 *   }
 *   ...
 * }
 * 
* * The call to {@code IpcDataCache.invalidateCache()} guarantees that all clients will re-fetch * birthdays from binder during consequent calls to * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock * held, we maintain consistency between different client views of the birthday state. The use of * IpcDataCache in this idiomatic way introduces no new race conditions. * * IpcDataCache has a few other features for doing things like incremental enhancement of cached * values and invalidation of multiple caches (that all share the same property key) at once. * * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each * time we update the cache. SELinux configuration must allow everyone to read this property * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write * the property. (These properties conventionally begin with the "cache_key." prefix.) * * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In this * local case, there's no IPC, so use of the cache is (depending on exact circumstance) * unnecessary. * * There may be queries for which it is more efficient to bypass the cache than to cache the * result. This would be true, for example, if some queries would require frequent cache * invalidation while other queries require infrequent invalidation. To expand on the birthday * example, suppose that there is a userId that signifies "the next birthday". When passed this * userId, the server returns the next birthday among all users - this value changes as time * advances. The userId value can be cached, but the cache must be invalidated whenever a * birthday occurs, and this invalidates all birthdays. If there is a large number of users, * invalidation will happen so often that the cache provides no value. * * The class provides a bypass mechanism to handle this situation. *
 * public class ActivityThread {
 *   ...
 *   private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
 *       new IpcDataCache.QueryHandler<Integer, Birthday>() {
 *           {@literal @}Override
 *           public Birthday apply(Integer) {
 *              return GetService("birthdayd").getUserBirthday(userId);
 *           }
 *           {@literal @}Override
 *           public boolean shouldBypassQuery(Integer userId) {
 *               return userId == NEXT_BIRTHDAY;
 *           }
 *       };
 *   ...
 * }
 * 
* * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that * particular query. The {@code shouldBypassQuery()} method is not abstract and the default * implementation returns false. * * For security, there is a allowlist of processes that are allowed to invalidate a cache. The * allowlist includes normal runtime processes but does not include test processes. Test * processes must call {@code IpcDataCache.disableForTestMode()} to disable all cache activity in * that process. * * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding. * * To test a binder cache, create one or more tests that exercise the binder method. This should * be done twice: once with production code and once with a special image that sets {@code DEBUG} * and {@code VERIFY} true. In the latter case, verify that no cache inconsistencies are * reported. If a cache inconsistency is reported, however, it might be a false positive. This * happens if the server side data can be read and written non-atomically with respect to cache * invalidation. * * @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 */ @TestApi @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @android.ravenwood.annotation.RavenwoodKeepWholeClass public class IpcDataCache extends PropertyInvalidatedCache { /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static abstract class QueryHandler extends PropertyInvalidatedCache.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 list of cache namespaces. Each namespace corresponds to an sepolicy domain. A * namespace is owned by a single process, although a single process can have more * than one namespace (system_server, as an example). * @hide */ @StringDef( prefix = { "MODULE_" }, value = { MODULE_TEST, MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY } ) @Retention(RetentionPolicy.SOURCE) public @interface IpcDataCacheModule { } /** * 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 String MODULE_TEST = PropertyInvalidatedCache.MODULE_TEST; /** * The module used for system server/framework caches. This is not visible outside * the system processes. * @hide */ @TestApi public static final String MODULE_SYSTEM = PropertyInvalidatedCache.MODULE_SYSTEM; /** * The module used for bluetooth caches. * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static final String MODULE_BLUETOOTH = PropertyInvalidatedCache.MODULE_BLUETOOTH; /** * 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 IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String cacheName, @NonNull QueryHandler computer) { super(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi @Override public void disableForCurrentProcess() { super.disableForCurrentProcess(); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi public static void disableForCurrentProcess(@NonNull String cacheName) { PropertyInvalidatedCache.disableForCurrentProcess(cacheName); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi @Override public @Nullable Result query(@NonNull Query query) { return super.query(query); } /** * {@inheritDoc} * @hide */ @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) @TestApi @Override public void invalidateCache() { super.invalidateCache(); } /** * 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(@NonNull @IpcDataCacheModule String module, @NonNull String api) { PropertyInvalidatedCache.invalidateCache(module, api); } /** * This is a convenience class that encapsulates configuration information for a cache. It * may be supplied to the cache constructors in lieu of the other parameters. The class * captures maximum entry count, the module, the key, and the api. The key is used to * invalidate the cache and may be shared by different caches. The api is a user-visible (in * debug) name for the cache. * * There are three specific use cases supported by this class. * * 1. Instance-per-cache: create a static instance of this class using the same * parameters as would have been given to IpcDataCache (or * PropertyInvalidatedCache). This static instance provides a hook for the * invalidateCache() and disableForLocalProcess() calls, which, generally, must * also be static. * * 2. Short-hand for shared configuration parameters: create an instance of this class * to capture the maximum number of entries and the module to be used by more than * one cache in the class. Refer to this instance when creating new configs. Only * the api and (optionally key) for the new cache must be supplied. * * 3. Tied caches: create a static instance of this class to capture the maximum * number of entries, the module, and the key. Refer to this instance when * creating a new config that differs in only the api. The new config can be * created as part of the cache constructor. All caches that trace back to the * root config share the same key and are invalidated by the invalidateCache() * method of the root config. All caches that trace back to the root config can be * disabled in the local process by the disableAllForCurrentProcess() method of the * root config. * * @hide */ public static class Config { final Args mArgs; final String mName; /** * The list of cache names that were created extending this Config. If * disableForCurrentProcess() is invoked on this config then all children will be * disabled. Furthermore, any new children based off of this config will be * disabled. The construction order guarantees that new caches will be disabled * before they are created (the Config must be created before the IpcDataCache is * created). */ private ArraySet mChildren; /** * True if registered children are disabled in the current process. If this is * true then all new children are disabled as they are registered. */ private boolean mDisabled = false; /** * Fully construct a config. */ private Config(@NonNull Args args, @NonNull String name) { mArgs = args; mName = name; } /** * */ public Config(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String name) { this(new Args(module).api(api).maxEntries(maxEntries), name); } /** * A short-hand constructor that makes the name the same as the api. */ public Config(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api) { this(maxEntries, module, api, api); } /** * Copy the module and max entries from the Config and take the api and name from * the parameter list. */ public Config(@NonNull Config root, @NonNull String api, @NonNull String name) { this(root.mArgs.api(api), name); } /** * Copy the module and max entries from the Config and take the api and name from * the parameter list. */ public Config(@NonNull Config root, @NonNull String api) { this(root.mArgs.api(api), api); } /** * Fetch a config that is a child of . The child shares the same api as the * parent and is registered with the parent for the purposes of disabling in the * current process. */ public Config child(@NonNull String name) { final Config result = new Config(mArgs, name); registerChild(name); return result; } /** * Set the cacheNull behavior. */ public Config cacheNulls(boolean enable) { return new Config(mArgs.cacheNulls(enable), mName); } /** * Set the isolateUidss behavior. */ public Config isolateUids(boolean enable) { return new Config(mArgs.isolateUids(enable), mName); } /** * Register a child cache name. If disableForCurrentProcess() has been called * against this cache, disable th new child. */ private final void registerChild(String name) { synchronized (this) { if (mChildren == null) { mChildren = new ArraySet<>(); } mChildren.add(name); if (mDisabled) { IpcDataCache.disableForCurrentProcess(name); } } } /** * Invalidate all caches that share this Config's module and api. */ public void invalidateCache() { IpcDataCache.invalidateCache(mArgs); } /** * Disable all caches that share this Config's name. */ public void disableForCurrentProcess() { IpcDataCache.disableForCurrentProcess(mName); } /** * Disable this cache and all children. Any child that is added in the future * will alwo be disabled. */ public void disableAllForCurrentProcess() { synchronized (this) { mDisabled = true; disableForCurrentProcess(); if (mChildren != null) { for (String c : mChildren) { IpcDataCache.disableForCurrentProcess(c); } } } } } /** * Create a new cache using a config. * @hide */ public IpcDataCache(@NonNull Config config, @NonNull QueryHandler computer) { super(config.mArgs, config.mName, computer); } /** * An interface suitable for a lambda expression instead of a QueryHandler applying remote call. * @hide */ public interface RemoteCall { Result apply(Query query) throws RemoteException; } /** * An interface suitable for a lambda expression instead of a QueryHandler bypassing the cache. * @hide */ public interface BypassCall { Boolean apply(Query query); } /** * This is a query handler that is created with a lambda expression that is invoked * every time the handler is called. The handler is specifically meant for services * hosted by system_server; the handler automatically rethrows RemoteException as a * RuntimeException, which is the usual handling for failed binder calls. */ private static class SystemServerCallHandler extends IpcDataCache.QueryHandler { private final RemoteCall mHandler; public SystemServerCallHandler(RemoteCall handler) { mHandler = handler; } @Override public Result apply(Query query) { try { return mHandler.apply(query); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } /** * Create a cache using a config and a lambda expression. * @param config The configuration for the cache. * @param remoteCall The lambda expression that will be invoked to fetch the data. * @hide */ public IpcDataCache(@NonNull Config config, @NonNull RemoteCall remoteCall) { this(config, android.multiuser.Flags.cachingDevelopmentImprovements() ? new QueryHandler() { @Override public Result apply(Query query) { try { return remoteCall.apply(query); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } : new SystemServerCallHandler<>(remoteCall)); } /** * Create a cache using a config and a lambda expression. * @param config The configuration for the cache. * @param remoteCall The lambda expression that will be invoked to fetch the data. * @param bypass The lambda expression that will be invoked to determine if the cache should be * bypassed. * @hide */ @FlaggedApi(android.multiuser.Flags.FLAG_CACHING_DEVELOPMENT_IMPROVEMENTS) public IpcDataCache(@NonNull Config config, @NonNull RemoteCall remoteCall, @NonNull BypassCall bypass) { this(config, new QueryHandler() { @Override public Result apply(Query query) { try { return remoteCall.apply(query); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @Override public boolean shouldBypassCache(Query query) { return bypass.apply(query); } }); } /** * The following APIs are exposed to support testing. They only forward the superclass but * that means the superclass does not have to expose the APIs itself. */ /** * Stop disabling local caches with the same name as . Any caches that are currently * 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 */ @TestApi @Override public final void forgetDisableLocal() { super.forgetDisableLocal(); } /** * Return whether a cache instance is disabled. * @hide */ @TestApi @Override public final boolean isDisabled() { return super.isDisabled(); } /** * This is an obsolete synonym for {@link #isDisabled()}. * @hide */ @TestApi public boolean getDisabledState() { return isDisabled(); } /** * Disable the use of this cache in this process. This method is used internally and during * testing. To disable a cache in normal code, use disableProcessLocal(). * @hide */ @TestApi @Override public final void disableInstance() { super.disableInstance(); } /** * Disable all caches that use the property as the current cache. * @hide */ @TestApi @Override public final void disableSystemWide() { super.disableSystemWide(); } /** * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is * illegal to clear the test mode if the test mode is already off. Enabling test mode puts * all caches in the process into test mode; all nonces are initialized to UNSET and * subsequent reads and writes are to process memory. This has the effect of disabling all * caches that are not local to the process. Disabling test mode restores caches to normal * operation. * @param mode The desired test mode. * @throws IllegalStateException if the supplied mode is already set. * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ @TestApi public static void setTestMode(boolean mode) { PropertyInvalidatedCache.setTestMode(mode); } /** * Enable or disable test mode. The protocol requires that the mode toggle: for instance, it is * illegal to clear the test mode if the test mode is already off. Enabling test mode puts * all caches in the process into test mode; all nonces are initialized to UNSET and * subsequent reads and writes are to process memory. This has the effect of disabling all * caches that are not local to the process. Disabling test mode restores caches to normal * operation. * @param mode The desired test mode. * @throws IllegalStateException if the supplied mode is already set. * @throws IllegalStateException if the process is not running an instrumentation test. * @hide */ @FlaggedApi(android.os.Flags.FLAG_IPC_DATA_CACHE_TEST_APIS) @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) public static void setCacheTestMode(boolean mode) { // Trunk-stable flagging requires that this API have a name different from the existing // setTestMode() API. However, the functionality is identical. setTestMode(mode); } }