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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "FrameworksMockingServicesTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.compat.overrides"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user