Merge "Create os/IpcDataCache" into tm-dev
This commit is contained in:
@@ -50,22 +50,6 @@ package android.app {
|
||||
method public void onCanceled(@NonNull android.app.PendingIntent);
|
||||
}
|
||||
|
||||
public class PropertyInvalidatedCache<Query, Result> {
|
||||
ctor public PropertyInvalidatedCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
|
||||
method public final void disableForCurrentProcess();
|
||||
method public final void invalidateCache();
|
||||
method public static void invalidateCache(@NonNull String, @NonNull String);
|
||||
method @Nullable public final Result query(@NonNull Query);
|
||||
field public static final String MODULE_BLUETOOTH = "bluetooth";
|
||||
field public static final String MODULE_TELEPHONY = "telephony";
|
||||
}
|
||||
|
||||
public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
|
||||
ctor public PropertyInvalidatedCache.QueryHandler();
|
||||
method @Nullable public abstract R apply(@NonNull Q);
|
||||
method public boolean shouldBypassCache(@NonNull Q);
|
||||
}
|
||||
|
||||
public class StatusBarManager {
|
||||
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
|
||||
}
|
||||
@@ -354,6 +338,22 @@ package android.os {
|
||||
field public static final int DEVICE_INITIAL_SDK_INT;
|
||||
}
|
||||
|
||||
public class IpcDataCache<Query, Result> {
|
||||
ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
|
||||
method public void disableForCurrentProcess();
|
||||
method public static void disableForCurrentProcess(@NonNull String);
|
||||
method public void invalidateCache();
|
||||
method public static void invalidateCache(@NonNull String, @NonNull String);
|
||||
method @Nullable public Result query(@NonNull Query);
|
||||
field public static final String MODULE_BLUETOOTH = "bluetooth";
|
||||
}
|
||||
|
||||
public abstract static class IpcDataCache.QueryHandler<Q, R> {
|
||||
ctor public IpcDataCache.QueryHandler();
|
||||
method @Nullable public abstract R apply(@NonNull Q);
|
||||
method public boolean shouldBypassCache(@NonNull Q);
|
||||
}
|
||||
|
||||
public interface Parcelable {
|
||||
method public default int getStability();
|
||||
}
|
||||
|
||||
@@ -373,16 +373,17 @@ package android.app {
|
||||
public class PropertyInvalidatedCache<Query, Result> {
|
||||
ctor public PropertyInvalidatedCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
|
||||
method @NonNull public static String createPropertyName(@NonNull String, @NonNull String);
|
||||
method public final void disableForCurrentProcess();
|
||||
method public void disableForCurrentProcess();
|
||||
method public static void disableForCurrentProcess(@NonNull String);
|
||||
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 void invalidateCache();
|
||||
method public static void invalidateCache(@NonNull String, @NonNull String);
|
||||
method public final boolean isDisabled();
|
||||
method @Nullable public final Result query(@NonNull Query);
|
||||
method @Nullable public Result query(@NonNull Query);
|
||||
method public static void setTestMode(boolean);
|
||||
method public void testPropertyName();
|
||||
field public static final String MODULE_BLUETOOTH = "bluetooth";
|
||||
@@ -1726,6 +1727,19 @@ package android.os {
|
||||
method @NonNull public static byte[] digest(@NonNull java.io.InputStream, @NonNull String) throws java.io.IOException, java.security.NoSuchAlgorithmException;
|
||||
}
|
||||
|
||||
public class IpcDataCache<Query, Result> extends android.app.PropertyInvalidatedCache<Query,Result> {
|
||||
ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
|
||||
method public static void disableForCurrentProcess(@NonNull String);
|
||||
method public static void invalidateCache(@NonNull String, @NonNull String);
|
||||
field public static final String MODULE_BLUETOOTH = "bluetooth";
|
||||
field public static final String MODULE_SYSTEM = "system_server";
|
||||
field public static final String MODULE_TEST = "test";
|
||||
}
|
||||
|
||||
public abstract static class IpcDataCache.QueryHandler<Q, R> extends android.app.PropertyInvalidatedCache.QueryHandler<Q,R> {
|
||||
ctor public IpcDataCache.QueryHandler();
|
||||
}
|
||||
|
||||
public final class MessageQueue {
|
||||
method public int postSyncBarrier();
|
||||
method public void removeSyncBarrier(int);
|
||||
|
||||
@@ -18,7 +18,6 @@ package android.app;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -137,6 +136,26 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* 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:
|
||||
* <list>
|
||||
* <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}.
|
||||
* Usually, the SELinux rules permit a process to write a system property (and therefore
|
||||
* invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that
|
||||
* although the cache can be constructed with any module string, whatever string is chosen must be
|
||||
* consistent with the SELinux configuration.
|
||||
* <ul> 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.
|
||||
* <ul> 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.
|
||||
* </list>
|
||||
*
|
||||
* User birthdays do occasionally change, so we have to modify the server to invalidate this
|
||||
* cache when necessary. That invalidation code looks like this:
|
||||
*
|
||||
@@ -192,25 +211,23 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* <pre>
|
||||
* public class ActivityThread {
|
||||
* ...
|
||||
* 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);
|
||||
* }
|
||||
* {@literal @}Override
|
||||
* protected boolean bypass(Integer userId) {
|
||||
* return userId == NEXT_BIRTHDAY;
|
||||
* }
|
||||
* };
|
||||
* 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;
|
||||
* }
|
||||
* };
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* If the {@code bypass()} method returns true then the cache is not used for that
|
||||
* particular query. The {@code bypass()} method is not abstract and the default
|
||||
* 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.
|
||||
@@ -231,14 +248,12 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* @param <Result> The class holding cache entries; use a boxed primitive if possible
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public class PropertyInvalidatedCache<Query, Result> {
|
||||
/**
|
||||
* This is a configuration class that customizes a cache instance.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public static abstract class QueryHandler<Q,R> {
|
||||
/**
|
||||
@@ -285,7 +300,6 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* The module used for bluetooth caches.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public static final String MODULE_BLUETOOTH = "bluetooth";
|
||||
|
||||
@@ -533,7 +547,6 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* @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, @NonNull String module, @NonNull String api,
|
||||
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
|
||||
@@ -792,7 +805,7 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* TODO(216112648) Remove this in favor of disableForCurrentProcess().
|
||||
* @hide
|
||||
*/
|
||||
public final void disableLocal() {
|
||||
public void disableLocal() {
|
||||
disableForCurrentProcess();
|
||||
}
|
||||
|
||||
@@ -802,12 +815,17 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* property.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public final void disableForCurrentProcess() {
|
||||
public void disableForCurrentProcess() {
|
||||
disableLocal(mCacheName);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@TestApi
|
||||
public static void disableForCurrentProcess(@NonNull String cacheName) {
|
||||
disableLocal(cacheName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether a cache instance is disabled.
|
||||
* @hide
|
||||
@@ -821,9 +839,8 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* Get a value from the cache or recompute it.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public final @Nullable Result query(@NonNull Query query) {
|
||||
public @Nullable Result query(@NonNull Query query) {
|
||||
// Let access to mDisabled race: it's atomic anyway.
|
||||
long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
|
||||
if (bypass(query)) {
|
||||
@@ -964,9 +981,8 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* PropertyInvalidatedCache is keyed on a particular property value.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public final void invalidateCache() {
|
||||
public void invalidateCache() {
|
||||
invalidateCache(mPropertyName);
|
||||
}
|
||||
|
||||
@@ -974,7 +990,6 @@ public class PropertyInvalidatedCache<Query, Result> {
|
||||
* 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 String module, @NonNull String api) {
|
||||
invalidateCache(createPropertyName(module, api));
|
||||
|
||||
364
core/java/android/os/IpcDataCache.java
Normal file
364
core/java/android/os/IpcDataCache.java
Normal file
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* 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.app.PropertyInvalidatedCache;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
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.HashSet;
|
||||
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:
|
||||
*
|
||||
* <pre>
|
||||
* parcelable Birthday {
|
||||
* int month;
|
||||
* int day;
|
||||
* }
|
||||
* interface IUserBirthdayService {
|
||||
* Birthday getUserBirthday(int userId);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Suppose the service implementation itself looks like this...
|
||||
*
|
||||
* <pre>
|
||||
* 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);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* ... and we have a client in frameworks (loaded into every app process) that looks like this:
|
||||
*
|
||||
* <pre>
|
||||
* public class ActivityThread {
|
||||
* ...
|
||||
* public Birthday getUserBirthday(int userId) {
|
||||
* return GetService("birthdayd").getUserBirthday(userId);
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* <pre>
|
||||
* 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);
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* 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:
|
||||
* <list>
|
||||
* <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}.
|
||||
* Usually, the SELinux rules permit a process to write a system property (and therefore
|
||||
* invalidate a cache) based on the wildcard {@code cache_key.<module>.*}. This means that
|
||||
* although the cache can be constructed with any module string, whatever string is chosen must be
|
||||
* consistent with the SELinux configuration.
|
||||
* <ul> 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.
|
||||
* <ul> 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.
|
||||
* </list>
|
||||
*
|
||||
* User birthdays do occasionally change, so we have to modify the server to invalidate this
|
||||
* cache when necessary. That invalidation code looks like this:
|
||||
*
|
||||
* <pre>
|
||||
* 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();
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* 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.
|
||||
* <pre>
|
||||
* 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;
|
||||
* }
|
||||
* };
|
||||
* ...
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* 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 <Query> The class used to index cache entries: must be hashable and comparable
|
||||
* @param <Result> The class holding cache entries; use a boxed primitive if possible
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, Result> {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES)
|
||||
@TestApi
|
||||
public static abstract class QueryHandler<Q,R>
|
||||
extends PropertyInvalidatedCache.QueryHandler<Q,R> {
|
||||
/**
|
||||
* Compute a result given a query. The semantics are those of Functor.
|
||||
*/
|
||||
public abstract @Nullable R apply(@NonNull Q query);
|
||||
|
||||
/**
|
||||
* Return true if a query should not use the cache. The default implementation
|
||||
* always uses the cache.
|
||||
*/
|
||||
public boolean shouldBypassCache(@NonNull Q query) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The 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 String module, @NonNull String api,
|
||||
@NonNull String cacheName, @NonNull QueryHandler<Query, Result> 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 String module, @NonNull String api) {
|
||||
PropertyInvalidatedCache.invalidateCache(module, api);
|
||||
}
|
||||
}
|
||||
312
core/tests/coretests/src/android/os/IpcDataCacheTest.java
Normal file
312
core/tests/coretests/src/android/os/IpcDataCacheTest.java
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test for verifying the behavior of {@link IpcDataCache}. This test does
|
||||
* not use any actual binder calls - it is entirely self-contained. This test also relies
|
||||
* on the test mode of {@link IpcDataCache} because Android SELinux rules do
|
||||
* not grant test processes the permission to set system properties.
|
||||
* <p>
|
||||
* Build/Install/Run:
|
||||
* atest FrameworksCoreTests:IpcDataCacheTest
|
||||
*/
|
||||
@SmallTest
|
||||
public class IpcDataCacheTest {
|
||||
|
||||
// Configuration for creating caches
|
||||
private static final String MODULE = IpcDataCache.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.
|
||||
private static class ServerProxy {
|
||||
// The number of times this class was queried.
|
||||
private int mCount = 0;
|
||||
|
||||
// A single query. The key behavior is that the query count is incremented.
|
||||
boolean query(int x) {
|
||||
mCount++;
|
||||
return value(x);
|
||||
}
|
||||
|
||||
// Return the expected value of an input, without incrementing the query count.
|
||||
boolean value(int x) {
|
||||
return x % 3 == 0;
|
||||
}
|
||||
|
||||
// Verify the count.
|
||||
void verify(int x) {
|
||||
assertEquals(x, mCount);
|
||||
}
|
||||
}
|
||||
|
||||
// The functions for querying the server.
|
||||
private static class ServerQuery
|
||||
extends IpcDataCache.QueryHandler<Integer, Boolean> {
|
||||
private final ServerProxy mServer;
|
||||
|
||||
ServerQuery(ServerProxy server) {
|
||||
mServer = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean apply(Integer x) {
|
||||
return mServer.query(x);
|
||||
}
|
||||
@Override
|
||||
public boolean shouldBypassCache(Integer x) {
|
||||
return x % 13 == 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the test mode after every test, in case this process is used for other
|
||||
// tests. This also resets the test property map.
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
IpcDataCache.setTestMode(false);
|
||||
}
|
||||
|
||||
// This test is disabled pending an sepolicy change that allows any app to set the
|
||||
// test property.
|
||||
@Test
|
||||
public void testBasicCache() {
|
||||
|
||||
// A stand-in for the binder. The test verifies that calls are passed through to
|
||||
// this class properly.
|
||||
ServerProxy tester = new ServerProxy();
|
||||
|
||||
// Create a cache that uses simple arithmetic to computer its values.
|
||||
IpcDataCache<Integer, Boolean> testCache =
|
||||
new IpcDataCache<>(4, MODULE, API, "testCache1",
|
||||
new ServerQuery(tester));
|
||||
|
||||
IpcDataCache.setTestMode(true);
|
||||
testCache.testPropertyName();
|
||||
|
||||
tester.verify(0);
|
||||
assertEquals(tester.value(3), testCache.query(3));
|
||||
tester.verify(1);
|
||||
assertEquals(tester.value(3), testCache.query(3));
|
||||
tester.verify(2);
|
||||
testCache.invalidateCache();
|
||||
assertEquals(tester.value(3), testCache.query(3));
|
||||
tester.verify(3);
|
||||
assertEquals(tester.value(5), testCache.query(5));
|
||||
tester.verify(4);
|
||||
assertEquals(tester.value(5), testCache.query(5));
|
||||
tester.verify(4);
|
||||
assertEquals(tester.value(3), testCache.query(3));
|
||||
tester.verify(4);
|
||||
|
||||
// Invalidate the cache, and verify that the next read on 3 goes to the server.
|
||||
testCache.invalidateCache();
|
||||
assertEquals(tester.value(3), testCache.query(3));
|
||||
tester.verify(5);
|
||||
|
||||
// Test bypass. The query for 13 always bypasses the cache.
|
||||
assertEquals(tester.value(12), testCache.query(12));
|
||||
assertEquals(tester.value(13), testCache.query(13));
|
||||
assertEquals(tester.value(14), testCache.query(14));
|
||||
tester.verify(8);
|
||||
assertEquals(tester.value(12), testCache.query(12));
|
||||
assertEquals(tester.value(13), testCache.query(13));
|
||||
assertEquals(tester.value(14), testCache.query(14));
|
||||
tester.verify(9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisableCache() {
|
||||
|
||||
// A stand-in for the binder. The test verifies that calls are passed through to
|
||||
// this class properly.
|
||||
ServerProxy tester = new ServerProxy();
|
||||
|
||||
// Three caches, all using the same system property but one uses a different name.
|
||||
IpcDataCache<Integer, Boolean> cache1 =
|
||||
new IpcDataCache<>(4, MODULE, API, "cacheA",
|
||||
new ServerQuery(tester));
|
||||
IpcDataCache<Integer, Boolean> cache2 =
|
||||
new IpcDataCache<>(4, MODULE, API, "cacheA",
|
||||
new ServerQuery(tester));
|
||||
IpcDataCache<Integer, Boolean> cache3 =
|
||||
new IpcDataCache<>(4, MODULE, API, "cacheB",
|
||||
new ServerQuery(tester));
|
||||
|
||||
// Caches are enabled upon creation.
|
||||
assertEquals(false, cache1.getDisabledState());
|
||||
assertEquals(false, cache2.getDisabledState());
|
||||
assertEquals(false, cache3.getDisabledState());
|
||||
|
||||
// Disable the cache1 instance. Only cache1 is disabled
|
||||
cache1.disableInstance();
|
||||
assertEquals(true, cache1.getDisabledState());
|
||||
assertEquals(false, cache2.getDisabledState());
|
||||
assertEquals(false, cache3.getDisabledState());
|
||||
|
||||
// Disable cache1. This will disable cache1 and cache2 because they share the
|
||||
// same name. cache3 has a different name and will not be disabled.
|
||||
cache1.disableForCurrentProcess();
|
||||
assertEquals(true, cache1.getDisabledState());
|
||||
assertEquals(true, cache2.getDisabledState());
|
||||
assertEquals(false, cache3.getDisabledState());
|
||||
|
||||
// Create a new cache1. Verify that the new instance is disabled.
|
||||
cache1 = new IpcDataCache<>(4, MODULE, API, "cacheA",
|
||||
new ServerQuery(tester));
|
||||
assertEquals(true, cache1.getDisabledState());
|
||||
|
||||
// Remove the record of caches being locally disabled. This is a clean-up step.
|
||||
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 IpcDataCache<>(4, MODULE, API, "cacheA",
|
||||
new ServerQuery(tester));
|
||||
assertEquals(false, cache1.getDisabledState());
|
||||
}
|
||||
|
||||
private static class TestQuery
|
||||
extends IpcDataCache.QueryHandler<Integer, String> {
|
||||
|
||||
private int mRecomputeCount = 0;
|
||||
|
||||
@Override
|
||||
public String apply(Integer qv) {
|
||||
mRecomputeCount += 1;
|
||||
return "foo" + qv.toString();
|
||||
}
|
||||
|
||||
int getRecomputeCount() {
|
||||
return mRecomputeCount;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestCache extends IpcDataCache<Integer, String> {
|
||||
private final TestQuery mQuery;
|
||||
|
||||
TestCache() {
|
||||
this(MODULE, API);
|
||||
}
|
||||
|
||||
TestCache(String module, String api) {
|
||||
this(module, api, new TestQuery());
|
||||
}
|
||||
|
||||
TestCache(String module, String api, TestQuery query) {
|
||||
super(4, module, api, "testCache7", query);
|
||||
mQuery = query;
|
||||
setTestMode(true);
|
||||
testPropertyName();
|
||||
}
|
||||
|
||||
int getRecomputeCount() {
|
||||
return mQuery.getRecomputeCount();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRecompute() {
|
||||
TestCache cache = new TestCache();
|
||||
cache.invalidateCache();
|
||||
assertEquals(cache.isDisabled(), false);
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(1, cache.getRecomputeCount());
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(1, cache.getRecomputeCount());
|
||||
assertEquals("foo6", cache.query(6));
|
||||
assertEquals(2, cache.getRecomputeCount());
|
||||
cache.invalidateCache();
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(3, cache.getRecomputeCount());
|
||||
// Invalidate the cache with a direct call to the property.
|
||||
IpcDataCache.invalidateCache(MODULE, API);
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(4, cache.getRecomputeCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheInitialState() {
|
||||
TestCache cache = new TestCache();
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(2, cache.getRecomputeCount());
|
||||
cache.invalidateCache();
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(3, cache.getRecomputeCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachePropertyUnset() {
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheDisableState() {
|
||||
TestCache cache = new TestCache();
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(2, cache.getRecomputeCount());
|
||||
cache.invalidateCache();
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(3, cache.getRecomputeCount());
|
||||
cache.disableSystemWide();
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(5, cache.getRecomputeCount());
|
||||
cache.invalidateCache(); // Should not reenable
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(7, cache.getRecomputeCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalProcessDisable() {
|
||||
TestCache cache = new TestCache();
|
||||
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.isDisabled(), false);
|
||||
cache.disableForCurrentProcess();
|
||||
assertEquals(cache.isDisabled(), true);
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals("foo5", cache.query(5));
|
||||
assertEquals(3, cache.getRecomputeCount());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user