All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.os.IpcDataCache Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.PropertyInvalidatedCache;
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.
 *
 * 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: * *
    The system property that controls the cache is named {@code cache_key..}. * Usually, the SELinux rules permit a process to write a system property (and therefore * invalidate a cache) based on the wildcard {@code cache_key..*}. This means that * although the cache can be constructed with any module string, whatever string is chosen must be * consistent with the SELinux configuration. *
      The API name can be any string of alphanumeric characters. All caches with the same API * are invalidated at the same time. If a server supports several caches and all are invalidated * in common, then it is most efficient to assign the same API string to every cache. *
        The cache name can be any string. In debug output, the name is used to distiguish between * caches with the same API name. The cache name is also used when disabling caches in the * current process. So, invalidation is based on the module+api but disabling (which is generally * a once-per-process operation) is based on the cache name. * * * 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) 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(maxEntries, module, 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. * * 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 { private final int mMaxEntries; @IpcDataCacheModule private final String mModule; private final String mApi; private 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; public Config(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String name) { mMaxEntries = maxEntries; mModule = module; mApi = api; mName = 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.maxEntries(), root.module(), 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.maxEntries(), root.module(), 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(this, api(), name); registerChild(name); return result; } public final int maxEntries() { return mMaxEntries; } @IpcDataCacheModule public final @NonNull String module() { return mModule; } public final @NonNull String api() { return mApi; } public final @NonNull String name() { return 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(mModule, mApi); } /** * 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.maxEntries(), config.module(), config.api(), config.name(), computer); } /** * An interface suitable for a lambda expression instead of a QueryHandler. * @hide */ public interface RemoteCall { Result apply(Query query) throws RemoteException; } /** * 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. * @hide */ public IpcDataCache(@NonNull Config config, @NonNull RemoteCall computer) { this(config, new SystemServerCallHandler<>(computer)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy