Merge "Add service for applying per-app compat overrides delivered via Device Config." into sc-v2-dev
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";
|
"com.android.server.connectivity.IpConnectivityMetrics";
|
||||||
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
|
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
|
||||||
"com.android.server.media.MediaCommunicationService";
|
"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 ROLE_SERVICE_CLASS = "com.android.role.RoleService";
|
||||||
private static final String GAME_MANAGER_SERVICE_CLASS =
|
private static final String GAME_MANAGER_SERVICE_CLASS =
|
||||||
@@ -2649,6 +2651,10 @@ public final class SystemServer implements Dumpable {
|
|||||||
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
|
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
|
||||||
t.traceEnd();
|
t.traceEnd();
|
||||||
|
|
||||||
|
t.traceBegin("AppCompatOverridesService");
|
||||||
|
mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
|
||||||
|
t.traceEnd();
|
||||||
|
|
||||||
ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
|
ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
|
||||||
START_BLOB_STORE_SERVICE);
|
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