Cache binder calls in CompatChanges
Cached call values are kept in ChangeIdStateCache (per-process cache). Use "cache_key.is_compat_change_enabled" system property to invalidate the cache (i.e. any value different from the last observed will trigger an invalidation). Any process can read that property, but only system server can change it. Bug: 140441727 Test: atest PlatformCompatTest Test: atest CompatConfigTest Test: atest CompatChanges Test: atest PlatformCompatGating Change-Id: Ibed398ae965b7da2432aaf8d5ffc259f7d228c7e
This commit is contained in:
86
core/java/android/app/compat/ChangeIdStateCache.java
Normal file
86
core/java/android/app/compat/ChangeIdStateCache.java
Normal file
@@ -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!");
|
||||
}
|
||||
}
|
||||
87
core/java/android/app/compat/ChangeIdStateQuery.java
Normal file
87
core/java/android/app/compat/ChangeIdStateQuery.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user