diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index df7bf7b94700b..add891d40d957 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -313,6 +313,7 @@ public class PropertyInvalidatedCache { * one of the permitted values above. The API is a string that is a legal Java simple * identifier. The api is modified to conform to the system property style guide by * replacing every upper case letter with an underscore and the lower case equivalent. + * (An initial upper case letter is not prefixed with an underscore). * There is no requirement that the apiName be the name of an actual API. * * Be aware that SystemProperties has a maximum length which is private to the @@ -326,7 +327,7 @@ public class PropertyInvalidatedCache { @NonNull String apiName) { char[] api = apiName.toCharArray(); int upper = 0; - for (int i = 0; i < api.length; i++) { + for (int i = 1; i < api.length; i++) { if (Character.isUpperCase(api[i])) { upper++; } @@ -336,7 +337,9 @@ public class PropertyInvalidatedCache { for (int i = 0; i < api.length; i++) { if (Character.isJavaIdentifierPart(api[i])) { if (Character.isUpperCase(api[i])) { - suffix[j++] = '_'; + if (i > 0) { + suffix[j++] = '_'; + } suffix[j++] = Character.toLowerCase(api[i]); } else { suffix[j++] = api[i]; @@ -1286,12 +1289,22 @@ public class PropertyInvalidatedCache { } /** - * Return the name of the cache, to be used in debug messages. + * Return the name of the cache, to be used in debug messages. This is exposed + * primarily for testing. + * @hide */ - private final @NonNull String cacheName() { + public final @NonNull String cacheName() { return mCacheName; } + /** + * Return the property used by the cache. This is primarily for test purposes. + * @hide + */ + public final @NonNull String propertyName() { + return mPropertyName; + } + /** * Return the query as a string, to be used in debug messages. New clients should not * override this, but should instead add the necessary toString() method to the Query diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index d375a9e1ba262..f5cf73b152c8c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -185,6 +185,30 @@ public class DevicePolicyManager { mResourcesManager = new DevicePolicyResourcesManager(context, service); } + /** + * Fetch the current value of mService. This is used in the binder cache lambda + * expressions. + */ + private final IDevicePolicyManager getService() { + return mService; + } + + /** + * Fetch the current value of mParentInstance. This is used in the binder cache + * lambda expressions. + */ + private final boolean isParentInstance() { + return mParentInstance; + } + + /** + * Fetch the current value of mContext. This is used in the binder cache lambda + * expressions. + */ + private final Context getContext() { + return mContext; + } + /** @hide test will override it. */ @VisibleForTesting protected int myUserId() { @@ -3783,57 +3807,21 @@ public class DevicePolicyManager { "android.app.extra.RESOURCE_IDS"; /** - * A convenience class that wraps some IpcDataCache methods. Instantiate it with an - * API string. Instances can and should be final static. All instances of this class - * use the same key for invalidation. + * This object is a single place to tack on invalidation and disable calls. All + * binder caches in this class derive from this Config, so all can be invalidated or + * disabled through this Config. */ - private static class BinderApi { - private final static String KEY = "DevicePolicyManager"; - private final String mApi; - BinderApi(String api) { - mApi = api; - } - final String api() { - return mApi; - } - final String key() { - return KEY; - } - final static void invalidate() { - IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, KEY); - } - final void disable() { - IpcDataCache.disableForCurrentProcess(mApi); - } - } + private static final IpcDataCache.Config sDpmCaches = + new IpcDataCache.Config(8, IpcDataCache.MODULE_SYSTEM, "DevicePolicyManagerCaches"); /** @hide */ public static void invalidateBinderCaches() { - BinderApi.invalidate(); + sDpmCaches.invalidateCache(); } - /** - * A simple wrapper for binder caches in this class. All caches are created with a - * maximum of 8 entries, the SYSTEM module, and a cache name that is the same as the api. - */ - private static class BinderCache extends IpcDataCache { - BinderCache(BinderApi api, IpcDataCache.QueryHandler handler) { - super(8, IpcDataCache.MODULE_SYSTEM, api.key(), api.api(), handler); - } - } - - /** - * Disable all caches in the local process. - * @hide - */ - public static void disableLocalProcessCaches() { - disableGetKeyguardDisabledFeaturesCache(); - disableHasDeviceOwnerCache(); - disableGetProfileOwnerOrDeviceOwnerSupervisionComponentCache(); - disableIsOrganizationOwnedDeviceWithManagedProfileCache(); - disableGetDeviceOwnerOrganizationNameCache(); - disableGetOrganizationNameForUserCache(); - disableIsNetworkLoggingEnabled(); + /** @hide */ + public static void disableLocalCaches() { + sDpmCaches.disableAllForCurrentProcess(); } /** @hide */ @@ -8435,54 +8423,16 @@ public class DevicePolicyManager { return getKeyguardDisabledFeatures(admin, myUserId()); } - // A key into the keyguard cache. - private static class KeyguardQuery { - private final ComponentName mAdmin; - private final int mUserHandle; - KeyguardQuery(@Nullable ComponentName admin, int userHandle) { - mAdmin = admin; - mUserHandle = userHandle; - } - public boolean equals(Object o) { - if (o instanceof KeyguardQuery) { - KeyguardQuery r = (KeyguardQuery) o; - return Objects.equals(mAdmin, r.mAdmin) && mUserHandle == r.mUserHandle; - } else { - return false; - } - } - public int hashCode() { - return ((mAdmin != null) ? mAdmin.hashCode() : 0) * 13 + mUserHandle; - } - } - - // The query handler does not cache wildcard user IDs, although they should never - // appear in the query. - private static final BinderApi sGetKeyguardDisabledFeatures = - new BinderApi("getKeyguardDisabledFeatures"); - private BinderCache mGetKeyGuardDisabledFeaturesCache = - new BinderCache<>(sGetKeyguardDisabledFeatures, - new IpcDataCache.QueryHandler() { - @Override - public Integer apply(KeyguardQuery query) { - try { - return mService.getKeyguardDisabledFeatures( - query.mAdmin, query.mUserHandle, mParentInstance); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - }}); - - /** @hide */ - public static void disableGetKeyguardDisabledFeaturesCache() { - sGetKeyguardDisabledFeatures.disable(); - } + private IpcDataCache, Integer> mGetKeyGuardDisabledFeaturesCache = + new IpcDataCache<>(sDpmCaches.child("getKeyguardDisabledFeatures"), + (query) -> getService().getKeyguardDisabledFeatures( + query.first, query.second, isParentInstance())); /** @hide per-user version */ @UnsupportedAppUsage public int getKeyguardDisabledFeatures(@Nullable ComponentName admin, int userHandle) { if (mService != null) { - return mGetKeyGuardDisabledFeaturesCache.query(new KeyguardQuery(admin, userHandle)); + return mGetKeyGuardDisabledFeaturesCache.query(new Pair<>(admin, userHandle)); } else { return KEYGUARD_DISABLE_FEATURES_NONE; } @@ -8864,23 +8814,9 @@ public class DevicePolicyManager { return name != null ? name.getPackageName() : null; } - private static final BinderApi sHasDeviceOwner = - new BinderApi("hasDeviceOwner"); - private BinderCache mHasDeviceOwnerCache = - new BinderCache<>(sHasDeviceOwner, - new IpcDataCache.QueryHandler() { - @Override - public Boolean apply(Void query) { - try { - return mService.hasDeviceOwner(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - }}); - /** @hide */ - public static void disableHasDeviceOwnerCache() { - sHasDeviceOwner.disable(); - } + private IpcDataCache mHasDeviceOwnerCache = + new IpcDataCache<>(sDpmCaches.child("hasDeviceOwner"), + (query) -> getService().hasDeviceOwner()); /** * Called by the system to find out whether the device is managed by a Device Owner. @@ -9256,25 +9192,10 @@ public class DevicePolicyManager { return null; } - private final static BinderApi sGetProfileOwnerOrDeviceOwnerSupervisionComponent = - new BinderApi("getProfileOwnerOrDeviceOwnerSupervisionComponent"); - private final BinderCache + private final IpcDataCache mGetProfileOwnerOrDeviceOwnerSupervisionComponentCache = - new BinderCache(sGetProfileOwnerOrDeviceOwnerSupervisionComponent, - new IpcDataCache.QueryHandler() { - @Override - public ComponentName apply(UserHandle user) { - try { - return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent( - user); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - }}); - /** @hide */ - public static void disableGetProfileOwnerOrDeviceOwnerSupervisionComponentCache() { - sGetProfileOwnerOrDeviceOwnerSupervisionComponent.disable(); - } + new IpcDataCache<>(sDpmCaches.child("getProfileOwnerOrDeviceOwnerSupervisionComponent"), + (arg) -> getService().getProfileOwnerOrDeviceOwnerSupervisionComponent(arg)); /** * Returns the configured supervision app if it exists and is the device owner or policy owner. @@ -9329,23 +9250,9 @@ public class DevicePolicyManager { return null; } - private final static BinderApi sIsOrganizationOwnedDeviceWithManagedProfile = - new BinderApi("isOrganizationOwnedDeviceWithManagedProfile"); - private final BinderCache mIsOrganizationOwnedDeviceWithManagedProfileCache = - new BinderCache(sIsOrganizationOwnedDeviceWithManagedProfile, - new IpcDataCache.QueryHandler() { - @Override - public Boolean apply(Void query) { - try { - return mService.isOrganizationOwnedDeviceWithManagedProfile(); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - }}); - /** @hide */ - public static void disableIsOrganizationOwnedDeviceWithManagedProfileCache() { - sIsOrganizationOwnedDeviceWithManagedProfile.disable(); - } + private final IpcDataCache mIsOrganizationOwnedDeviceWithManagedProfileCache = + new IpcDataCache(sDpmCaches.child("isOrganizationOwnedDeviceWithManagedProfile"), + (query) -> getService().isOrganizationOwnedDeviceWithManagedProfile()); /** * Apps can use this method to find out if the device was provisioned as @@ -12927,23 +12834,9 @@ public class DevicePolicyManager { } } - private final static BinderApi sGetDeviceOwnerOrganizationName = - new BinderApi("getDeviceOwnerOrganizationName"); - private final BinderCache mGetDeviceOwnerOrganizationNameCache = - new BinderCache(sGetDeviceOwnerOrganizationName, - new IpcDataCache.QueryHandler() { - @Override - public CharSequence apply(Void query) { - try { - return mService.getDeviceOwnerOrganizationName(); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - }}); - /** @hide */ - public static void disableGetDeviceOwnerOrganizationNameCache() { - sGetDeviceOwnerOrganizationName.disable(); - } + private final IpcDataCache mGetDeviceOwnerOrganizationNameCache = + new IpcDataCache(sDpmCaches.child("getDeviceOwnerOrganizationName"), + (query) -> getService().getDeviceOwnerOrganizationName()); /** * Called by the system to retrieve the name of the organization managing the device. @@ -12960,23 +12853,9 @@ public class DevicePolicyManager { return mGetDeviceOwnerOrganizationNameCache.query(null); } - private final static BinderApi sGetOrganizationNameForUser = - new BinderApi("getOrganizationNameForUser"); - private final BinderCache mGetOrganizationNameForUserCache = - new BinderCache(sGetOrganizationNameForUser, - new IpcDataCache.QueryHandler() { - @Override - public CharSequence apply(Integer userHandle) { - try { - return mService.getOrganizationNameForUser(userHandle); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - }}); - /** @hide */ - public static void disableGetOrganizationNameForUserCache() { - sGetOrganizationNameForUser.disable(); - } + private final IpcDataCache mGetOrganizationNameForUserCache = + new IpcDataCache<>(sDpmCaches.child("getOrganizationNameForUser"), + (query) -> getService().getOrganizationNameForUser(query)); /** * Retrieve the default title message used in the confirm credentials screen for a given user. @@ -13372,24 +13251,10 @@ public class DevicePolicyManager { } } - private final static BinderApi sNetworkLoggingApi = new BinderApi("isNetworkLoggingEnabled"); - private BinderCache mIsNetworkLoggingEnabledCache = - new BinderCache<>(sNetworkLoggingApi, - new IpcDataCache.QueryHandler() { - @Override - public Boolean apply(ComponentName admin) { - try { - return mService.isNetworkLoggingEnabled( - admin, mContext.getPackageName()); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - }}); - - /** @hide */ - public static void disableIsNetworkLoggingEnabled() { - sNetworkLoggingApi.disable(); - } + private IpcDataCache mIsNetworkLoggingEnabledCache = + new IpcDataCache<>(sDpmCaches.child("isNetworkLoggingEnabled"), + (admin) -> getService().isNetworkLoggingEnabled(admin, + getContext().getPackageName())); /** * Return whether network logging is enabled by a device owner or profile owner of diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index 0efa34cdc47fe..bf44d65c40023 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -23,7 +23,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.PropertyInvalidatedCache; import android.text.TextUtils; -import android.util.Log; +import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastPrintWriter; @@ -35,7 +35,6 @@ 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; @@ -324,8 +323,8 @@ public class IpcDataCache extends PropertyInvalidatedCache computer) { + @NonNull String api, @NonNull String cacheName, + @NonNull QueryHandler computer) { super(maxEntries, module, api, cacheName, computer); } @@ -382,4 +381,210 @@ public class IpcDataCache extends PropertyInvalidatedCache 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)); + } } diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java index fa7d7214d2899..34712ce54e0ff 100644 --- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java +++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java @@ -53,6 +53,14 @@ public class IpcDataCacheTest { return value(x); } + // A single query but this can throw an exception. + boolean query(int x, boolean y) throws RemoteException { + if (y) { + throw new RemoteException(); + } + return query(x); + } + // Return the expected value of an input, without incrementing the query count. boolean value(int x) { return x % 3 == 0; @@ -138,6 +146,47 @@ public class IpcDataCacheTest { tester.verify(9); } + // This test is disabled pending an sepolicy change that allows any app to set the + // test property. + @Test + public void testRemoteCall() { + + // 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.Config config = new IpcDataCache.Config(4, MODULE, API, "testCache2"); + IpcDataCache testCache = + new IpcDataCache<>(config, (x) -> tester.query(x, x % 10 == 9)); + + 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); + + try { + testCache.query(9); + assertEquals(false, true); // The code should not reach this point. + } catch (RuntimeException e) { + assertEquals(e.getCause() instanceof RemoteException, true); + } + tester.verify(4); + } + @Test public void testDisableCache() { @@ -225,6 +274,17 @@ public class IpcDataCacheTest { testPropertyName(); } + TestCache(IpcDataCache.Config c) { + this(c, new TestQuery()); + } + + TestCache(IpcDataCache.Config c, TestQuery query) { + super(c, query); + mQuery = query; + setTestMode(true); + testPropertyName(); + } + int getRecomputeCount() { return mQuery.getRecomputeCount(); } @@ -309,4 +369,48 @@ public class IpcDataCacheTest { assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } + + @Test + public void testConfig() { + IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA"); + TestCache ac = new TestCache(a); + assertEquals(8, a.maxEntries()); + assertEquals(MODULE, a.module()); + assertEquals("apiA", a.api()); + assertEquals("apiA", a.name()); + IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB"); + TestCache bc = new TestCache(b); + assertEquals(8, b.maxEntries()); + assertEquals(MODULE, b.module()); + assertEquals("apiB", b.api()); + assertEquals("apiB", b.name()); + IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC"); + TestCache cc = new TestCache(c); + assertEquals(8, c.maxEntries()); + assertEquals(MODULE, c.module()); + assertEquals("apiC", c.api()); + assertEquals("nameC", c.name()); + IpcDataCache.Config d = a.child("nameD"); + TestCache dc = new TestCache(d); + assertEquals(8, d.maxEntries()); + assertEquals(MODULE, d.module()); + assertEquals("apiA", d.api()); + assertEquals("nameD", d.name()); + + a.disableForCurrentProcess(); + assertEquals(ac.isDisabled(), true); + assertEquals(bc.isDisabled(), false); + assertEquals(cc.isDisabled(), false); + assertEquals(dc.isDisabled(), false); + + a.disableAllForCurrentProcess(); + assertEquals(ac.isDisabled(), true); + assertEquals(bc.isDisabled(), false); + assertEquals(cc.isDisabled(), false); + assertEquals(dc.isDisabled(), true); + + IpcDataCache.Config e = a.child("nameE"); + TestCache ec = new TestCache(e); + assertEquals(ec.isDisabled(), true); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 268d588bd9070..35dbb157aaf45 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -1781,7 +1781,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @VisibleForTesting DevicePolicyManagerService(Injector injector) { - DevicePolicyManager.disableGetKeyguardDisabledFeaturesCache(); + DevicePolicyManager.disableLocalCaches(); mInjector = injector; mContext = Objects.requireNonNull(injector.mContext);