diff options
6 files changed, 191 insertions, 27 deletions
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java new file mode 100644 index 000000000000..9ef63f6587f0 --- /dev/null +++ b/core/java/android/app/compat/ChangeIdStateCache.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 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.app.compat; + +import android.app.PropertyInvalidatedCache; +import android.content.Context; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.compat.IPlatformCompat; + +/** + * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat} + * @hide + */ +public final class ChangeIdStateCache + extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> { + private static final String CACHE_KEY = "cache_key.is_compat_change_enabled"; + private static final int MAX_ENTRIES = 20; + private static boolean sDisabled = false; + + /** @hide */ + public ChangeIdStateCache() { + super(MAX_ENTRIES, CACHE_KEY); + } + + /** + * Disable cache. + * + * <p>Should only be used in unit tests. + * @hide + */ + public static void disable() { + sDisabled = true; + } + + /** + * Invalidate the cache. + * + * <p>Can only be called by the system server process. + * @hide + */ + public static void invalidate() { + if (!sDisabled) { + PropertyInvalidatedCache.invalidateCache(CACHE_KEY); + } + } + + @Override + protected Boolean recompute(ChangeIdStateQuery query) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + final long token = Binder.clearCallingIdentity(); + try { + if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) { + return platformCompat.isChangeEnabledByPackageName(query.changeId, + query.packageName, + query.userId); + } else if (query.type == ChangeIdStateQuery.QUERY_BY_UID) { + return platformCompat.isChangeEnabledByUid(query.changeId, query.uid); + } else { + throw new IllegalArgumentException("Invalid query type: " + query.type); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(token); + } + throw new IllegalStateException("Could not recompute value!"); + } +} diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java new file mode 100644 index 000000000000..2c4c120672ab --- /dev/null +++ b/core/java/android/app/compat/ChangeIdStateQuery.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 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.app.compat; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + + +/** + * A key type for caching calls to {@link com.android.internal.compat.IPlatformCompat} + * + * <p>For {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByPackageName} + * and {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByUid} + * + * @hide + */ +final class ChangeIdStateQuery { + + static final int QUERY_BY_PACKAGE_NAME = 0; + static final int QUERY_BY_UID = 1; + @IntDef({QUERY_BY_PACKAGE_NAME, QUERY_BY_UID}) + @Retention(RetentionPolicy.SOURCE) + @interface QueryType {} + + public @QueryType int type; + public long changeId; + public String packageName; + public int uid; + public int userId; + + private ChangeIdStateQuery(@QueryType int type, long changeId, String packageName, + int uid, int userId) { + this.type = type; + this.changeId = changeId; + this.packageName = packageName; + this.uid = uid; + this.userId = userId; + } + + static ChangeIdStateQuery byPackageName(long changeId, @NonNull String packageName, + int userId) { + return new ChangeIdStateQuery(QUERY_BY_PACKAGE_NAME, changeId, packageName, 0, userId); + } + + static ChangeIdStateQuery byUid(long changeId, int uid) { + return new ChangeIdStateQuery(QUERY_BY_UID, changeId, null, uid, 0); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if ((other == null) || !(other instanceof ChangeIdStateQuery)) { + return false; + } + final ChangeIdStateQuery that = (ChangeIdStateQuery) other; + return this.type == that.type + && this.changeId == that.changeId + && Objects.equals(this.packageName, that.packageName) + && this.uid == that.uid + && this.userId == that.userId; + } + + @Override + public int hashCode() { + return Objects.hash(type, changeId, packageName, uid, userId); + } +} diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index e289a2775b79..0d5e45f9e5d4 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -19,14 +19,8 @@ package android.app.compat; import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.Compatibility; -import android.content.Context; -import android.os.Binder; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; -import com.android.internal.compat.IPlatformCompat; - /** * CompatChanges APIs - to be used by platform code only (including mainline * modules). @@ -35,6 +29,7 @@ import com.android.internal.compat.IPlatformCompat; */ @SystemApi public final class CompatChanges { + private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache(); private CompatChanges() {} /** @@ -69,17 +64,8 @@ public final class CompatChanges { */ public static boolean isChangeEnabled(long changeId, @NonNull String packageName, @NonNull UserHandle user) { - IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - final long token = Binder.clearCallingIdentity(); - try { - return platformCompat.isChangeEnabledByPackageName(changeId, packageName, - user.getIdentifier()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); - } + return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName, + user.getIdentifier())); } /** @@ -101,15 +87,7 @@ public final class CompatChanges { * @return {@code true} if the change is enabled for the current app. */ public static boolean isChangeEnabled(long changeId, int uid) { - IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - final long token = Binder.clearCallingIdentity(); - try { - return platformCompat.isChangeEnabledByUid(changeId, uid); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - Binder.restoreCallingIdentity(token); - } + return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid)); } + } diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index f15d999e1006..cc5b7668e028 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -16,6 +16,7 @@ package com.android.server.compat; +import android.app.compat.ChangeIdStateCache; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -78,6 +79,7 @@ final class CompatConfig { void addChange(CompatChange change) { synchronized (mChanges) { mChanges.put(change.getId(), change); + invalidateCache(); } } @@ -170,6 +172,7 @@ final class CompatConfig { addChange(c); } c.addPackageOverride(packageName, enabled); + invalidateCache(); } return alreadyKnown; } @@ -226,6 +229,7 @@ final class CompatConfig { // Should never occur, since validator is in the same process. throw new RuntimeException("Unable to call override validator!", e); } + invalidateCache(); } return overrideExists; } @@ -248,6 +252,7 @@ final class CompatConfig { addOverride(changeId, packageName, false); } + invalidateCache(); } } @@ -277,6 +282,7 @@ final class CompatConfig { throw new RuntimeException("Unable to call override validator!", e); } } + invalidateCache(); } } @@ -396,4 +402,8 @@ final class CompatConfig { IOverrideValidator getOverrideValidator() { return mOverrideValidator; } + + private void invalidateCache() { + ChangeIdStateCache.invalidate(); + } } diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index 407f67e2fd8e..44f4ccf69cdd 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.app.compat.ChangeIdStateCache; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -74,6 +75,7 @@ public class CompatConfigTest { // Assume userdebug/eng non-final build when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); + ChangeIdStateCache.disable(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index ce5d6d9be770..717a78de7497 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -57,6 +57,7 @@ public class PlatformCompatTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + android.app.compat.ChangeIdStateCache.disable(); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow( new PackageManager.NameNotFoundException()); |