Add service for applying per-app compat overrides delivered via Device Config.

This CL only adds the listener on device config changes, package
added/changed/removed support will be added in a follow up.

Bug: 190024878
Test: atest FrameworksMockingServicesTests:AppCompatOverridesServiceTest
Test: atest FrameworksMockingServicesTests:AppCompatOverridesParserTest
Change-Id: Ifda798abca5ed97cd4699dfd1244053ab2796586
This commit is contained in:
tomnatan
2021-06-25 11:41:13 +00:00
parent 7d2d139462
commit 843601987f
6 changed files with 1362 additions and 0 deletions

View File

@@ -0,0 +1,383 @@
/*
* Copyright (C) 2021 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 com.android.server.compat.overrides;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import android.app.compat.PackageOverride;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Slog;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* A utility class for parsing App Compat Overrides flags.
*
* @hide
*/
final class AppCompatOverridesParser {
/**
* Flag for specifying all compat change IDs owned by a namespace. See {@link
* #parseOwnedChangeIds} for information on how this flag is parsed.
*/
static final String FLAG_OWNED_CHANGE_IDS = "owned_change_ids";
/**
* Flag for immediately removing overrides for certain packages and change IDs (from the compat
* platform), as well as stopping to apply them, in case of an emergency. See {@link
* #parseRemoveOverrides} for information on how this flag is parsed.
*/
static final String FLAG_REMOVE_OVERRIDES = "remove_overrides";
private static final String TAG = "AppCompatOverridesParser";
private static final String WILDCARD_SYMBOL = "*";
private static final Pattern BOOLEAN_PATTERN =
Pattern.compile("true|false", Pattern.CASE_INSENSITIVE);
private static final String WILDCARD_NO_OWNED_CHANGE_IDS_WARNING =
"Wildcard can't be used in '" + FLAG_REMOVE_OVERRIDES + "' flag with an empty "
+ FLAG_OWNED_CHANGE_IDS + "' flag";
private final PackageManager mPackageManager;
AppCompatOverridesParser(PackageManager packageManager) {
mPackageManager = packageManager;
}
/**
* Parses the given {@code configStr} and returns a map from package name to a set of change
* IDs to remove for that package.
*
* <p>The given {@code configStr} is expected to either be:
*
* <ul>
* <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code
* ownedChangeIds}, for all installed packages should be removed.
* <li>A comma separated key value list, where the key is a package name and the value is
* either:
* <ul>
* <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code
* ownedChangeIds} for that package should be removed.
* <li>A colon separated list of change IDs to remove for that package.
* </ul>
* </ul>
*
* <p>If the given {@code configStr} doesn't match the expected format, an empty map will be
* returned. If a specific change ID isn't a valid long, it will be ignored.
*/
Map<String, Set<Long>> parseRemoveOverrides(String configStr, Set<Long> ownedChangeIds) {
if (configStr.isEmpty()) {
return emptyMap();
}
Map<String, Set<Long>> result = new ArrayMap<>();
if (configStr.equals(WILDCARD_SYMBOL)) {
if (ownedChangeIds.isEmpty()) {
Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING);
return emptyMap();
}
List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
MATCH_ANY_USER);
for (ApplicationInfo appInfo : installedApps) {
result.put(appInfo.packageName, ownedChangeIds);
}
return result;
}
KeyValueListParser parser = new KeyValueListParser(',');
try {
parser.setString(configStr);
} catch (IllegalArgumentException e) {
Slog.w(
TAG,
"Invalid format in '" + FLAG_REMOVE_OVERRIDES + "' flag: " + configStr, e);
return emptyMap();
}
for (int i = 0; i < parser.size(); i++) {
String packageName = parser.keyAt(i);
String changeIdsStr = parser.getString(packageName, /* def= */ "");
if (changeIdsStr.equals(WILDCARD_SYMBOL)) {
if (ownedChangeIds.isEmpty()) {
Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING);
continue;
}
result.put(packageName, ownedChangeIds);
} else {
for (String changeIdStr : changeIdsStr.split(":")) {
try {
long changeId = Long.parseLong(changeIdStr);
result.computeIfAbsent(packageName, k -> new ArraySet<>()).add(changeId);
} catch (NumberFormatException e) {
Slog.w(
TAG,
"Invalid change ID in '" + FLAG_REMOVE_OVERRIDES + "' flag: "
+ changeIdStr, e);
}
}
}
}
return result;
}
/**
* Parses the given {@code configStr}, that is expected to be a comma separated list of change
* IDs, into a set.
*
* <p>If any of the change IDs isn't a valid long, it will be ignored.
*/
static Set<Long> parseOwnedChangeIds(String configStr) {
if (configStr.isEmpty()) {
return emptySet();
}
Set<Long> result = new ArraySet<>();
for (String changeIdStr : configStr.split(",")) {
try {
result.add(Long.parseLong(changeIdStr));
} catch (NumberFormatException e) {
Slog.w(TAG,
"Invalid change ID in '" + FLAG_OWNED_CHANGE_IDS + "' flag: " + changeIdStr,
e);
}
}
return result;
}
/**
* Parses the given {@code configStr}, that is expected to be a comma separated list of changes
* overrides, and returns a {@link PackageOverrides}.
*
* <p>Each change override is in the following format:
* '<change-id>:<min-version-code?>:<max-version-code?>:<enabled?>'. If <enabled> is empty,
* this indicates that any override for the specified change ID should be removed.
*
* <p>If there are multiple overrides that should be added with the same change ID, the one
* that best fits the given {@code versionCode} is added.
*
* <p>Any overrides whose change ID is in {@code changeIdsToSkip} are ignored.
*
* <p>If a change override entry in {@code configStr} is invalid, it will be ignored. If the
* same change ID is both added and removed, i.e., has a change override entry with an empty
* enabled and another with a non-empty enabled, the change ID will only be removed.
*/
static PackageOverrides parsePackageOverrides(
String configStr, long versionCode, Set<Long> changeIdsToSkip) {
if (configStr.isEmpty()) {
return new PackageOverrides();
}
PackageOverrideComparator comparator = new PackageOverrideComparator(versionCode);
Map<Long, PackageOverride> overridesToAdd = new ArrayMap<>();
Set<Long> overridesToRemove = new ArraySet<>();
for (String overrideEntryString : configStr.split(",")) {
List<String> changeIdAndVersions = Arrays.asList(overrideEntryString.split(":", 4));
if (changeIdAndVersions.size() != 4) {
Slog.w(TAG, "Invalid change override entry: " + overrideEntryString);
continue;
}
long changeId;
try {
changeId = Long.parseLong(changeIdAndVersions.get(0));
} catch (NumberFormatException e) {
Slog.w(TAG, "Invalid change ID in override entry: " + overrideEntryString, e);
continue;
}
if (changeIdsToSkip.contains(changeId)) {
continue;
}
String minVersionCodeStr = changeIdAndVersions.get(1);
String maxVersionCodeStr = changeIdAndVersions.get(2);
String enabledStr = changeIdAndVersions.get(3);
if (enabledStr.isEmpty()) {
if (!minVersionCodeStr.isEmpty() || !maxVersionCodeStr.isEmpty()) {
Slog.w(
TAG,
"min/max version code should be empty if enabled is empty: "
+ overrideEntryString);
}
overridesToRemove.add(changeId);
continue;
}
if (!BOOLEAN_PATTERN.matcher(enabledStr).matches()) {
Slog.w(TAG, "Invalid enabled string in override entry: " + overrideEntryString);
continue;
}
boolean enabled = Boolean.parseBoolean(enabledStr);
PackageOverride.Builder overrideBuilder = new PackageOverride.Builder().setEnabled(
enabled);
try {
if (!minVersionCodeStr.isEmpty()) {
overrideBuilder.setMinVersionCode(Long.parseLong(minVersionCodeStr));
}
if (!maxVersionCodeStr.isEmpty()) {
overrideBuilder.setMaxVersionCode(Long.parseLong(maxVersionCodeStr));
}
} catch (NumberFormatException e) {
Slog.w(TAG,
"Invalid min/max version code in override entry: " + overrideEntryString,
e);
continue;
}
try {
PackageOverride override = overrideBuilder.build();
if (!overridesToAdd.containsKey(changeId)
|| comparator.compare(override, overridesToAdd.get(changeId)) < 0) {
overridesToAdd.put(changeId, override);
}
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed to build PackageOverride", e);
}
}
for (Long changeId : overridesToRemove) {
if (overridesToAdd.containsKey(changeId)) {
Slog.w(
TAG,
"Change ID ["
+ changeId
+ "] is both added and removed in package override flag: "
+ configStr);
overridesToAdd.remove(changeId);
}
}
return new PackageOverrides(overridesToAdd, overridesToRemove);
}
/**
* A container for a map from change ID to {@link PackageOverride} to add and a set of change
* IDs to remove overrides for.
*
* <p>The map of overrides to add and the set of overrides to remove are mutually exclusive.
*/
static final class PackageOverrides {
public final Map<Long, PackageOverride> overridesToAdd;
public final Set<Long> overridesToRemove;
PackageOverrides() {
this(emptyMap(), emptySet());
}
PackageOverrides(Map<Long, PackageOverride> overridesToAdd, Set<Long> overridesToRemove) {
this.overridesToAdd = overridesToAdd;
this.overridesToRemove = overridesToRemove;
}
}
/**
* A {@link Comparator} that compares @link PackageOverride} instances with respect to a
* specified {@code versionCode} as follows:
*
* <ul>
* <li>Prefer the {@link PackageOverride} whose version range contains {@code versionCode}.
* <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code
* versionCode} from below.
* <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code
* versionCode} from above.
* </ul>
*/
private static final class PackageOverrideComparator implements Comparator<PackageOverride> {
private final long mVersionCode;
PackageOverrideComparator(long versionCode) {
this.mVersionCode = versionCode;
}
@Override
public int compare(PackageOverride o1, PackageOverride o2) {
// Prefer overrides whose version range contains versionCode.
boolean isVersionInRange1 = isVersionInRange(o1, mVersionCode);
boolean isVersionInRange2 = isVersionInRange(o2, mVersionCode);
if (isVersionInRange1 != isVersionInRange2) {
return isVersionInRange1 ? -1 : 1;
}
// Otherwise, prefer overrides whose version range is before versionCode.
boolean isVersionAfterRange1 = isVersionAfterRange(o1, mVersionCode);
boolean isVersionAfterRange2 = isVersionAfterRange(o2, mVersionCode);
if (isVersionAfterRange1 != isVersionAfterRange2) {
return isVersionAfterRange1 ? -1 : 1;
}
// If both overrides' version ranges are either before or after versionCode, prefer
// those whose version range is closer to versionCode.
return Long.compare(
getVersionProximity(o1, mVersionCode), getVersionProximity(o2, mVersionCode));
}
/**
* Returns true if the version range in the given {@code override} contains {@code
* versionCode}.
*/
private static boolean isVersionInRange(PackageOverride override, long versionCode) {
return override.getMinVersionCode() <= versionCode
&& versionCode <= override.getMaxVersionCode();
}
/**
* Returns true if the given {@code versionCode} is strictly after the version range in the
* given {@code override}.
*/
private static boolean isVersionAfterRange(PackageOverride override, long versionCode) {
return override.getMaxVersionCode() < versionCode;
}
/**
* Returns true if the given {@code versionCode} is strictly before the version range in the
* given {@code override}.
*/
private static boolean isVersionBeforeRange(PackageOverride override, long versionCode) {
return override.getMinVersionCode() > versionCode;
}
/**
* In case the given {@code versionCode} is strictly before or after the version range in
* the given {@code override}, returns the distance from it, otherwise returns zero.
*/
private static long getVersionProximity(PackageOverride override, long versionCode) {
if (isVersionAfterRange(override, versionCode)) {
return versionCode - override.getMaxVersionCode();
}
if (isVersionBeforeRange(override, versionCode)) {
return override.getMinVersionCode() - versionCode;
}
// Version is in range. Note that when two overrides have a zero version proximity
// they will be ordered arbitrarily.
return 0;
}
}
}

View File

@@ -0,0 +1,279 @@
/*
* Copyright (C) 2021 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 com.android.server.compat.overrides;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES;
import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
import static java.util.Collections.emptySet;
import android.annotation.Nullable;
import android.app.compat.PackageOverride;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.server.SystemService;
import com.android.server.compat.overrides.AppCompatOverridesParser.PackageOverrides;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Service for applying per-app compat overrides delivered via Device Config.
*
* <p>The service listens both on changes to supported Device Config namespaces and on package
* added/changed/removed events, and applies overrides accordingly.
*
* @hide
*/
public final class AppCompatOverridesService {
private static final String TAG = "AppCompatOverridesService";
private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(
NAMESPACE_APP_COMPAT_OVERRIDES);
private final Context mContext;
private final PackageManager mPackageManager;
private final IPlatformCompat mPlatformCompat;
private final List<String> mSupportedNamespaces;
private final List<DeviceConfigListener> mDeviceConfigListeners;
private final AppCompatOverridesParser mOverridesParser;
private AppCompatOverridesService(Context context) {
this(context, IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)), SUPPORTED_NAMESPACES);
}
@VisibleForTesting
AppCompatOverridesService(Context context, IPlatformCompat platformCompat,
List<String> supportedNamespaces) {
mContext = context;
mPackageManager = mContext.getPackageManager();
mPlatformCompat = platformCompat;
mSupportedNamespaces = supportedNamespaces;
mDeviceConfigListeners = new ArrayList<>();
mOverridesParser = new AppCompatOverridesParser(mPackageManager);
}
@Override
public void finalize() {
unregisterDeviceConfigListeners();
}
@VisibleForTesting
void registerDeviceConfigListeners() {
for (String namespace : mSupportedNamespaces) {
DeviceConfigListener listener = new DeviceConfigListener(namespace);
DeviceConfig.addOnPropertiesChangedListener(namespace, mContext.getMainExecutor(),
listener);
mDeviceConfigListeners.add(listener);
}
}
private void unregisterDeviceConfigListeners() {
for (DeviceConfigListener listener : mDeviceConfigListeners) {
DeviceConfig.removeOnPropertiesChangedListener(listener);
}
}
/**
* Same as {@link #applyOverrides(Properties, Map)} except all properties of the given {@code
* namespace} are fetched via {@link DeviceConfig#getProperties}.
*/
private void applyAllOverrides(String namespace,
Map<String, Set<Long>> packageToChangeIdsToSkip) {
applyOverrides(DeviceConfig.getProperties(namespace), packageToChangeIdsToSkip);
}
/**
* Iterates all package override flags in the given {@code properties}, and for each flag whose
* package is installed on the device, parses its value and applies the overrides in it with
* respect to the package's current installed version.
*/
private void applyOverrides(Properties properties,
Map<String, Set<Long>> packageToChangeIdsToSkip) {
Set<String> packageNames = new ArraySet<>(properties.getKeyset());
packageNames.remove(FLAG_OWNED_CHANGE_IDS);
packageNames.remove(FLAG_REMOVE_OVERRIDES);
for (String packageName : packageNames) {
Long versionCode = getVersionCodeOrNull(packageName);
if (versionCode == null) {
// Package isn't installed yet.
continue;
}
applyPackageOverrides(properties.getString(packageName, /* defaultValue= */ ""),
packageName, versionCode,
packageToChangeIdsToSkip.getOrDefault(packageName, emptySet()));
}
}
/**
* Calls {@link AppCompatOverridesParser#parsePackageOverrides} on the given arguments, adds the
* resulting {@link PackageOverrides#overridesToAdd} via {@link
* IPlatformCompat#putOverridesOnReleaseBuilds}, and removes the resulting {@link
* PackageOverrides#overridesToRemove} via {@link
* IPlatformCompat#removeOverridesOnReleaseBuilds}.
*/
private void applyPackageOverrides(String configStr, String packageName,
long versionCode, Set<Long> changeIdsToSkip) {
PackageOverrides packageOverrides = AppCompatOverridesParser.parsePackageOverrides(
configStr, versionCode, changeIdsToSkip);
putPackageOverrides(packageName, packageOverrides.overridesToAdd);
removePackageOverrides(packageName, packageOverrides.overridesToRemove);
}
/**
* Calls {@link IPlatformCompat#removeOverridesOnReleaseBuilds} on each package name and
* respective change IDs in {@code overridesToRemove}.
*/
private void removeOverrides(Map<String, Set<Long>> overridesToRemove) {
for (Map.Entry<String, Set<Long>> packageNameAndOverrides : overridesToRemove.entrySet()) {
removePackageOverrides(packageNameAndOverrides.getKey(),
packageNameAndOverrides.getValue());
}
}
/**
* Fetches the value of {@link AppCompatOverridesParser#FLAG_REMOVE_OVERRIDES} for the given
* {@code namespace} and parses it into a map from package name to a set of change IDs to
* remove for that package.
*/
private Map<String, Set<Long>> getOverridesToRemove(String namespace) {
return mOverridesParser.parseRemoveOverrides(
DeviceConfig.getString(namespace, FLAG_REMOVE_OVERRIDES, /* defaultValue= */ ""),
getOwnedChangeIds(namespace));
}
/**
* Fetches the value of {@link AppCompatOverridesParser#FLAG_OWNED_CHANGE_IDS} for the given
* {@code namespace} and parses it into a set of change IDs.
*/
private static Set<Long> getOwnedChangeIds(String namespace) {
return AppCompatOverridesParser.parseOwnedChangeIds(
DeviceConfig.getString(namespace, FLAG_OWNED_CHANGE_IDS, /* defaultValue= */ ""));
}
private void putPackageOverrides(String packageName,
Map<Long, PackageOverride> overridesToAdd) {
if (overridesToAdd.isEmpty()) {
return;
}
CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overridesToAdd);
try {
mPlatformCompat.putOverridesOnReleaseBuilds(config, packageName);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
}
}
private void removePackageOverrides(String packageName, Set<Long> overridesToRemove) {
if (overridesToRemove.isEmpty()) {
return;
}
CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
overridesToRemove);
try {
mPlatformCompat.removeOverridesOnReleaseBuilds(config, packageName);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call IPlatformCompat#removeOverridesOnReleaseBuilds", e);
}
}
@Nullable
private Long getVersionCodeOrNull(String packageName) {
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
MATCH_ANY_USER);
return applicationInfo.longVersionCode;
} catch (PackageManager.NameNotFoundException e) {
// Package isn't installed yet.
return null;
}
}
/**
* SystemService lifecycle for AppCompatOverridesService.
*
* @hide
*/
public static final class Lifecycle extends SystemService {
private AppCompatOverridesService mService;
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
mService = new AppCompatOverridesService(getContext());
mService.registerDeviceConfigListeners();
}
}
/**
* A {@link DeviceConfig.OnPropertiesChangedListener} that listens on changes to a given
* namespace and adds/removes overrides according to the changed flags.
*/
private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
private final String mNamespace;
private DeviceConfigListener(String namespace) {
mNamespace = namespace;
}
@Override
public void onPropertiesChanged(Properties properties) {
boolean removeOverridesFlagChanged = properties.getKeyset().contains(
FLAG_REMOVE_OVERRIDES);
boolean ownedChangedIdsFlagChanged = properties.getKeyset().contains(
FLAG_OWNED_CHANGE_IDS);
Map<String, Set<Long>> overridesToRemove = getOverridesToRemove(mNamespace);
if (removeOverridesFlagChanged || ownedChangedIdsFlagChanged) {
// In both cases it's possible that overrides that weren't removed before should
// now be removed.
removeOverrides(overridesToRemove);
}
if (removeOverridesFlagChanged) {
// We need to re-apply all overrides in the namespace since the remove overrides
// flag might have blocked some of them from being applied before.
applyAllOverrides(mNamespace, overridesToRemove);
} else {
applyOverrides(properties, overridesToRemove);
}
}
}
}

View File

@@ -0,0 +1,12 @@
{
"presubmit": [
{
"name": "FrameworksMockingServicesTests",
"options": [
{
"include-filter": "com.android.server.compat.overrides"
}
]
}
]
}

View File

@@ -377,6 +377,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.connectivity.IpConnectivityMetrics";
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
"com.android.server.media.MediaCommunicationService";
private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
"com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
private static final String GAME_MANAGER_SERVICE_CLASS =
@@ -2649,6 +2651,10 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
t.traceEnd();
t.traceBegin("AppCompatOverridesService");
mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
t.traceEnd();
ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
START_BLOB_STORE_SERVICE);

View File

@@ -0,0 +1,302 @@
/*
* Copyright (C) 2021 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 com.android.server.compat.overrides;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptySet;
import android.app.compat.PackageOverride;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import com.android.server.compat.overrides.AppCompatOverridesParser.PackageOverrides;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
/**
* Test class for {@link AppCompatOverridesParser}.
*
* Build/Install/Run:
* atest FrameworksMockingServicesTests:AppCompatOverridesParserTest
*/
@RunWith(MockitoJUnitRunner.class)
@SmallTest
@Presubmit
public class AppCompatOverridesParserTest {
private static final String PACKAGE_1 = "com.android.test1";
private static final String PACKAGE_2 = "com.android.test2";
private static final String PACKAGE_3 = "com.android.test3";
private static final String PACKAGE_4 = "com.android.test4";
private AppCompatOverridesParser mParser;
@Mock
private PackageManager mPackageManager;
@Before
public void setUp() throws Exception {
mParser = new AppCompatOverridesParser(mPackageManager);
}
@Test
public void parseRemoveOverrides_emptyConfig_returnsEmpty() {
Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
assertThat(mParser.parseRemoveOverrides("", ownedChangeIds)).isEmpty();
}
@Test
public void parseRemoveOverrides_configHasWildcardNoOwnedChangeIds_returnsEmpty() {
when(mPackageManager.getInstalledApplications(anyInt()))
.thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2)));
assertThat(mParser.parseRemoveOverrides("*", /* ownedChangeIds= */ emptySet())).isEmpty();
}
@Test
public void parseRemoveOverrides_configHasWildcard_returnsAllInstalledPackagesToAllOwnedIds() {
Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
when(mPackageManager.getInstalledApplications(anyInt()))
.thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2),
createAppInfo(PACKAGE_3)));
Map<String, Set<Long>> result = mParser.parseRemoveOverrides("*", ownedChangeIds);
assertThat(result).hasSize(3);
assertThat(result.get(PACKAGE_1)).containsExactly(123L, 456L);
assertThat(result.get(PACKAGE_2)).containsExactly(123L, 456L);
assertThat(result.get(PACKAGE_3)).containsExactly(123L, 456L);
}
@Test
public void parseRemoveOverrides_configHasInvalidWildcardSymbol_returnsEmpty() {
Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
when(mPackageManager.getInstalledApplications(anyInt())).thenReturn(
Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2)));
assertThat(mParser.parseRemoveOverrides("**", ownedChangeIds)).isEmpty();
}
@Test
public void parseRemoveOverrides_configHasSingleEntry_returnsPackageToChangeIds() {
Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
PACKAGE_1 + "=12:34", /* ownedChangeIds= */ emptySet());
assertThat(result).hasSize(1);
assertThat(result.get(PACKAGE_1)).containsExactly(12L, 34L);
}
@Test
public void parseRemoveOverrides_configHasMultipleEntries_returnsPackagesToChangeIds() {
Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L, 56L, 78L));
Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
PACKAGE_1 + "=12," + PACKAGE_2 + "=*," + PACKAGE_3 + "=12:56:78," + PACKAGE_4
+ "=", ownedChangeIds);
assertThat(result).hasSize(3);
assertThat(result.get(PACKAGE_1)).containsExactly(12L);
assertThat(result.get(PACKAGE_2)).containsExactly(12L, 34L, 56L, 78L);
assertThat(result.get(PACKAGE_3)).containsExactly(12L, 56L, 78L);
}
@Test
public void parseRemoveOverrides_configHasPackageWithWildcardNoOwnedId_returnsWithoutPackage() {
Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
PACKAGE_1 + "=*," + PACKAGE_2 + "=12", /* ownedChangeIds= */ emptySet());
assertThat(result).hasSize(1);
assertThat(result.get(PACKAGE_2)).containsExactly(12L);
}
@Test
public void parseRemoveOverrides_configHasInvalidKeyValueListFormat_returnsEmpty() {
Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L));
assertThat(mParser.parseRemoveOverrides(
PACKAGE_1 + "=12," + PACKAGE_2 + ">34", ownedChangeIds)).isEmpty();
}
@Test
public void parseRemoveOverrides_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() {
Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
PACKAGE_1 + "=12," + PACKAGE_2 + "=12:56L:78," + PACKAGE_3
+ "=34L", /* ownedChangeIds= */ emptySet());
assertThat(result).hasSize(2);
assertThat(result.get(PACKAGE_1)).containsExactly(12L);
assertThat(result.get(PACKAGE_2)).containsExactly(12L, 78L);
}
@Test
public void parseOwnedChangeIds_emptyConfig_returnsEmpty() {
assertThat(AppCompatOverridesParser.parseOwnedChangeIds("")).isEmpty();
}
@Test
public void parseOwnedChangeIds_configHasSingleChangeId_returnsChangeId() {
assertThat(AppCompatOverridesParser.parseOwnedChangeIds("123")).containsExactly(123L);
}
@Test
public void parseOwnedChangeIds_configHasMultipleChangeIds_returnsChangeIds() {
assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,34,56")).containsExactly(12L,
34L, 56L);
}
@Test
public void parseOwnedChangeIds_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() {
// We add a valid entry before and after the invalid ones to make sure they are applied.
assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,C34,56")).containsExactly(12L,
56L);
}
@Test
public void parsePackageOverrides_emptyConfig_returnsEmpty() {
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"", /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
assertThat(result.overridesToAdd).isEmpty();
assertThat(result.overridesToRemove).isEmpty();
}
@Test
public void parsePackageOverrides_configWithSingleOverride_returnsOverride() {
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"123:::true", /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
assertThat(result.overridesToAdd).hasSize(1);
assertThat(result.overridesToAdd.get(123L)).isEqualTo(
new PackageOverride.Builder().setEnabled(true).build());
}
@Test
public void parsePackageOverrides_configWithMultipleOverridesToAdd_returnsOverrides() {
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"910:3:4:false,78:10::false,12:::false,34:1:2:true,34:10::true,56::2:true,"
+ "56:3:4:false,34:4:8:true,78:6:7:true,910:5::true,1112::5:true,"
+ "56:6::true,1112:6:7:false", /* versionCode= */
5, /* changeIdsToSkip= */ emptySet());
assertThat(result.overridesToAdd).hasSize(6);
assertThat(result.overridesToAdd.get(12L)).isEqualTo(
new PackageOverride.Builder().setEnabled(false).build());
assertThat(result.overridesToAdd.get(34L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(4).setMaxVersionCode(8).setEnabled(
true).build());
assertThat(result.overridesToAdd.get(56L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(3).setMaxVersionCode(4).setEnabled(
false).build());
assertThat(result.overridesToAdd.get(78L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(6).setMaxVersionCode(7).setEnabled(
true).build());
assertThat(result.overridesToAdd.get(910L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(5).setEnabled(true).build());
assertThat(result.overridesToAdd.get(1112L)).isEqualTo(
new PackageOverride.Builder().setMaxVersionCode(5).setEnabled(true).build());
assertThat(result.overridesToRemove).isEmpty();
}
@Test
public void parsePackageOverrides_configWithMultipleOverridesToRemove_returnsOverrides() {
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"12:::,34:1:2:", /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
assertThat(result.overridesToRemove).containsExactly(12L, 34L);
assertThat(result.overridesToAdd).isEmpty();
}
@Test
public void parsePackageOverrides_configWithBothOverridesToAddAndRemove_returnsOverrides() {
// Note that change 56 is both added and removed, therefore it will only be removed.
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"56:::,12:::true,34:::,56:3:7:true", /* versionCode= */ 5, /* changeIdsToSkip= */
emptySet());
assertThat(result.overridesToAdd).hasSize(1);
assertThat(result.overridesToAdd.get(12L)).isEqualTo(
new PackageOverride.Builder().setEnabled(true).build());
assertThat(result.overridesToRemove).containsExactly(34L, 56L);
}
@Test
public void parsePackageOverrides_changeIdsToSkipSpecified_returnsWithoutChangeIdsToSkip() {
ArraySet<Long> changeIdsToSkip = new ArraySet<>();
changeIdsToSkip.add(34L);
changeIdsToSkip.add(56L);
changeIdsToSkip.add(910L);
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"12:::true,34:::,56:3:7:true,78:::", /* versionCode= */ 5, changeIdsToSkip);
assertThat(result.overridesToAdd).hasSize(1);
assertThat(result.overridesToAdd.get(12L)).isEqualTo(
new PackageOverride.Builder().setEnabled(true).build());
assertThat(result.overridesToRemove).containsExactly(78L);
}
@Test
public void parsePackageOverrides_changeIdsToSkipContainsAllIds_returnsEmpty() {
ArraySet<Long> changeIdsToSkip = new ArraySet<>();
changeIdsToSkip.add(12L);
changeIdsToSkip.add(34L);
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"12:::true,34:::", /* versionCode= */ 5, changeIdsToSkip);
assertThat(result.overridesToAdd).isEmpty();
assertThat(result.overridesToRemove).isEmpty();
}
@Test
public void parsePackageOverrides_someOverridesAreInvalid_returnsWithoutInvalidOverrides() {
// We add a valid entry before and after the invalid ones to make sure they are applied.
PackageOverrides result = AppCompatOverridesParser.parsePackageOverrides(/* configStr= */
"12:::True,56:1:2:FALSE,56:3:true,78:4:8:true:,C1:::true,910:::no,"
+ "1112:1:ten:true,1112:one:10:true,,1314:7:3:false,34:one:ten:",
/* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
assertThat(result.overridesToAdd).hasSize(2);
assertThat(result.overridesToAdd.get(12L)).isEqualTo(
new PackageOverride.Builder().setEnabled(true).build());
assertThat(result.overridesToAdd.get(56L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(1).setMaxVersionCode(2).setEnabled(
false).build());
assertThat(result.overridesToRemove).containsExactly(34L);
}
private static ApplicationInfo createAppInfo(String packageName) {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.packageName = packageName;
return appInfo;
}
}

View File

@@ -0,0 +1,380 @@
/*
* Copyright (C) 2021 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 com.android.server.compat.overrides;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.app.compat.PackageOverride;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.server.testables.TestableDeviceConfig.TestableDeviceConfigRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Test class for {@link AppCompatOverridesService}.
*
* Build/Install/Run:
* atest FrameworksMockingServicesTests:AppCompatOverridesServiceTest
*/
@RunWith(MockitoJUnitRunner.class)
@SmallTest
@Presubmit
public class AppCompatOverridesServiceTest {
private static final String NAMESPACE_1 = "namespace_1";
private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(NAMESPACE_1);
private static final String PACKAGE_1 = "com.android.test1";
private static final String PACKAGE_2 = "com.android.test2";
private static final String PACKAGE_3 = "com.android.test3";
private static final String PACKAGE_4 = "com.android.test4";
private static final String PACKAGE_5 = "com.android.test5";
private MockContext mMockContext;
private AppCompatOverridesService mService;
@Mock
private PackageManager mPackageManager;
@Mock
private IPlatformCompat mPlatformCompat;
@Captor
private ArgumentCaptor<CompatibilityOverrideConfig> mOverridesToAddConfigCaptor;
@Captor
private ArgumentCaptor<CompatibilityOverridesToRemoveConfig> mOverridesToRemoveConfigCaptor;
@Rule
public TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfigRule();
class MockContext extends ContextWrapper {
MockContext(Context base) {
super(base);
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
@Override
public Executor getMainExecutor() {
// Run on current thread
return Runnable::run;
}
}
@Before
public void setUp() throws Exception {
mMockContext = new MockContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
mService = new AppCompatOverridesService(mMockContext, mPlatformCompat,
SUPPORTED_NAMESPACES);
}
@Test
public void onPropertiesChanged_removeOverridesFlagNotSet_appliesPackageOverrides()
throws Exception {
mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 3);
mockGetApplicationInfoNotInstalled(PACKAGE_2);
mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 10);
mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 1);
mockGetApplicationInfo(PACKAGE_5, /* versionCode= */ 1);
mService.registerDeviceConfigListeners();
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(PACKAGE_1, "123:::true,456::1:false,456:2::true")
.setString(PACKAGE_2, "123:::true")
.setString(PACKAGE_3, "123:1:9:true,123:10:11:false,123:11::true,456:::")
.setString(PACKAGE_4, "")
.setString(PACKAGE_5, "123:::,789:::")
.setString(FLAG_OWNED_CHANGE_IDS, "123,456,789").build());
Map<Long, PackageOverride> addedOverrides;
// Package 1
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_1));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides;
assertThat(addedOverrides).hasSize(2);
assertThat(addedOverrides.get(123L)).isEqualTo(
new PackageOverride.Builder().setEnabled(true).build());
assertThat(addedOverrides.get(456L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build());
// Package 2
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
// Package 3
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_3));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides;
assertThat(addedOverrides).hasSize(1);
assertThat(addedOverrides.get(123L)).isEqualTo(
new PackageOverride.Builder().setMinVersionCode(10).setMaxVersionCode(
11).setEnabled(false).build());
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L);
// Package 4
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_4));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
// Package 5
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_5));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_5));
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 789L);
}
@Test
public void onPropertiesChanged_removeOverridesFlagSetBefore_skipsOverridesToRemove()
throws Exception {
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456," + PACKAGE_2 + "=123")
.setString(PACKAGE_1, "123:::true")
.setString(PACKAGE_4, "123:::true").build());
mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
mService.registerDeviceConfigListeners();
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(PACKAGE_1, "123:::true,456:::,789:::false")
.setString(PACKAGE_2, "123:::true")
.setString(PACKAGE_3, "456:::true").build());
// Package 1
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_1));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(789L);
// Package 2
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
// Package 3
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_3));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L);
// Package 4 (not applied because it hasn't changed after the listener was added)
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_4));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
}
@Test
public void onPropertiesChanged_removeOverridesFlagChangedNoPackageOverridesFlags_removesOnly()
throws Exception {
mService.registerDeviceConfigListeners();
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(FLAG_REMOVE_OVERRIDES,
PACKAGE_1 + "=123:456," + PACKAGE_2 + "=789").build());
// Package 1
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L);
// Package 2
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(789L);
}
@Test
public void onPropertiesChanged_removeOverridesFlagAndSomePackageOverrideFlagsChanged_ok()
throws Exception {
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456")
.setString(PACKAGE_1, "123:::true,456:::,789:::false")
.setString(PACKAGE_3, "456:::false,789:::true").build());
mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
mService.registerDeviceConfigListeners();
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(FLAG_REMOVE_OVERRIDES, PACKAGE_2 + "=123," + PACKAGE_3 + "=789")
.setString(PACKAGE_2, "123:::true,456:::").build());
// Package 1
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_1));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L,
789L);
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L);
// Package 2
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
List<CompatibilityOverridesToRemoveConfig> configs =
mOverridesToRemoveConfigCaptor.getAllValues();
assertThat(configs.size()).isAtLeast(2);
assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(123L);
assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(456L);
// Package 3
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_3));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L);
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(789L);
}
@Test
public void onPropertiesChanged_ownedChangeIdsFlagAndSomePackageOverrideFlagsChanged_ok()
throws Exception {
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=*")
.setString(FLAG_OWNED_CHANGE_IDS, "123,456")
.setString(PACKAGE_1, "123:::true")
.setString(PACKAGE_3, "456:::false").build());
mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
mService.registerDeviceConfigListeners();
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
.setString(PACKAGE_2, "123:::true").build());
// Package 1
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L,
789L);
// Package 2
verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
eq(PACKAGE_2));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L);
// Package 3
verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_3));
verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
}
@Test
public void onPropertiesChanged_platformCompatThrowsExceptionForSomeCalls_skipsFailedCalls()
throws Exception {
mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 0);
doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds(
any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
doThrow(new RemoteException()).when(mPlatformCompat).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
mService.registerDeviceConfigListeners();
DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
.setString(PACKAGE_1, "123:::true,456:::")
.setString(PACKAGE_2, "123:::true,456:::")
.setString(PACKAGE_3, "123:::true,456:::")
.setString(PACKAGE_4, "123:::true,456:::").build());
// Package 1
verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
eq(PACKAGE_1));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
// Package 2
verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
eq(PACKAGE_2));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
// Package 3
verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
eq(PACKAGE_3));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
// Package 4
verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
eq(PACKAGE_1));
verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
}
private void mockGetApplicationInfo(String packageName, long versionCode)
throws Exception {
when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())).thenReturn(
createAppInfo(versionCode));
}
private void mockGetApplicationInfoNotInstalled(String packageName) throws Exception {
when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
.thenThrow(new PackageManager.NameNotFoundException());
}
private static ApplicationInfo createAppInfo(long versionCode) {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.longVersionCode = versionCode;
return appInfo;
}
}