/* * 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: *
* 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:
*
* 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 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 QueryHandlercomputer) { 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); } }