diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fea9972c662cf..e0a1b3e4a3d03 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11510,7 +11510,14 @@ public final class Settings {
/**
* The packages whitelisted to be run in autofill compatibility mode. The list
- * of packages is ":" colon delimited.
+ * of packages is {@code ":"} colon delimited, and each entry has the name of the
+ * package and an optional list of url bar resource ids (the list is delimited by
+ * brackets&mdash{@code [} and {@code ]}&mdash and is also comma delimited).
+ *
+ *
For example, a list with 3 packages {@code p1}, {@code p2}, and {@code p3}, where
+ * package {@code p1} have one id ({@code url_bar}, {@code p2} has none, and {@code p3 }
+ * have 2 ids {@code url_foo} and {@code url_bas}) would be
+ * {@code p1[url_bar]:p2:p3[url_foo,url_bas]}
*
* @hide
*/
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index 70c4ec07ee4eb..de234559d53e1 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -32,7 +32,6 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Pair;
import android.util.Xml;
import com.android.internal.R;
@@ -79,7 +78,7 @@ public final class AutofillServiceInfo {
private final String mSettingsActivity;
@Nullable
- private final ArrayMap> mCompatibilityPackages;
+ private final ArrayMap mCompatibilityPackages;
public AutofillServiceInfo(Context context, ComponentName comp, int userHandle)
throws PackageManager.NameNotFoundException {
@@ -117,7 +116,7 @@ public final class AutofillServiceInfo {
}
String settingsActivity = null;
- ArrayMap> compatibilityPackages = null;
+ ArrayMap compatibilityPackages = null;
try {
final Resources resources = context.getPackageManager().getResourcesForApplication(
@@ -153,10 +152,9 @@ public final class AutofillServiceInfo {
mCompatibilityPackages = compatibilityPackages;
}
- private ArrayMap> parseCompatibilityPackages(XmlPullParser parser,
- Resources resources)
- throws IOException, XmlPullParserException {
- ArrayMap> compatibilityPackages = null;
+ private ArrayMap parseCompatibilityPackages(XmlPullParser parser,
+ Resources resources) throws IOException, XmlPullParserException {
+ ArrayMap compatibilityPackages = null;
final int outerDepth = parser.getDepth();
int type;
@@ -200,13 +198,18 @@ public final class AutofillServiceInfo {
} else {
maxVersionCode = Long.MAX_VALUE;
}
- final String urlBarResourceId = cpAttributes.getString(
- R.styleable.AutofillService_CompatibilityPackage_urlBarResourceId);
+ if (true) { // TODO(b/74445943): remove block after P DP2 is branched
+ final String urlBarResourceId = cpAttributes.getString(
+ R.styleable.AutofillService_CompatibilityPackage_urlBarResourceId);
+ if (urlBarResourceId != null) {
+ Log.e(TAG, "Service is using deprecated attribute 'urlBarResourceId'");
+ }
+ }
if (compatibilityPackages == null) {
compatibilityPackages = new ArrayMap<>();
}
- compatibilityPackages.put(name, new Pair<>(maxVersionCode, urlBarResourceId));
+ compatibilityPackages.put(name, maxVersionCode);
} finally {
XmlUtils.skipCurrentTag(parser);
if (cpAttributes != null) {
@@ -228,23 +231,10 @@ public final class AutofillServiceInfo {
return mSettingsActivity;
}
- public ArrayMap> getCompatibilityPackages() {
+ public ArrayMap getCompatibilityPackages() {
return mCompatibilityPackages;
}
- /**
- * Gets the resource id of the URL bar for a package. Used in compat mode
- */
- // TODO: return a list of strings instead
- @Nullable
- public String getUrlBarResourceId(String packageName) {
- if (mCompatibilityPackages == null) {
- return null;
- }
- final Pair pair = mCompatibilityPackages.get(packageName);
- return pair == null ? null : pair.second;
- }
-
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 021e88ea6093d..6d23fab54ae31 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7978,9 +7978,7 @@
android.content.pm.PackageInfo#getLongVersionCode()} for the target package.
-->
-
+
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7d5d1ba1366b0..2c0deedc435f8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2869,6 +2869,7 @@
+
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 266b2d791991e..8a66414358aa5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -59,9 +59,7 @@ import android.service.autofill.UserData;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.LocalLog;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -73,6 +71,7 @@ import android.view.autofill.IAutoFillManager;
import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.IResultReceiver;
@@ -88,8 +87,8 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
-import java.util.Set;
/**
* Entry point service for autofill management.
@@ -105,6 +104,9 @@ public final class AutofillManagerService extends SystemService {
static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions";
private static final char COMPAT_PACKAGE_DELIMITER = ':';
+ private static final char COMPAT_PACKAGE_URL_IDS_DELIMITER = ',';
+ private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN = '[';
+ private static final char COMPAT_PACKAGE_URL_IDS_BLOCK_END = ']';
private final Context mContext;
private final AutoFillUI mUi;
@@ -326,7 +328,7 @@ public final class AutofillManagerService extends SystemService {
if (service == null) {
service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi,
- mDisabledUsers.get(resolvedUserId));
+ mAutofillCompatState, mDisabledUsers.get(resolvedUserId));
mServicesCache.put(userId, service);
addCompatibilityModeRequestsLocked(service, userId);
}
@@ -544,23 +546,24 @@ public final class AutofillManagerService extends SystemService {
private void addCompatibilityModeRequestsLocked(@NonNull AutofillManagerServiceImpl service
, int userId) {
mAutofillCompatState.reset();
- final ArrayMap> compatPackages =
+ final ArrayMap compatPackages =
service.getCompatibilityPackagesLocked();
if (compatPackages == null || compatPackages.isEmpty()) {
return;
}
- final Set whiteListedPackages = getWhitelistedCompatModePackages();
+
+ final Map whiteListedPackages = getWhitelistedCompatModePackages();
final int compatPackageCount = compatPackages.size();
for (int i = 0; i < compatPackageCount; i++) {
final String packageName = compatPackages.keyAt(i);
- if (whiteListedPackages == null || !whiteListedPackages.contains(packageName)) {
+ if (whiteListedPackages == null || !whiteListedPackages.containsKey(packageName)) {
Slog.w(TAG, "Ignoring not whitelisted compat package " + packageName);
continue;
}
- final Long maxVersionCode = compatPackages.valueAt(i).first;
+ final Long maxVersionCode = compatPackages.valueAt(i);
if (maxVersionCode != null) {
mAutofillCompatState.addCompatibilityModeRequest(packageName,
- maxVersionCode, userId);
+ maxVersionCode, whiteListedPackages.get(packageName), userId);
}
}
}
@@ -571,16 +574,60 @@ public final class AutofillManagerService extends SystemService {
Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES);
}
- private @Nullable Set getWhitelistedCompatModePackages() {
- final String compatPackagesSetting = getWhitelistedCompatModePackagesFromSettings();
- if (TextUtils.isEmpty(compatPackagesSetting)) {
+ @Nullable
+ private Map getWhitelistedCompatModePackages() {
+ return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings());
+ }
+
+ @Nullable
+ @VisibleForTesting
+ static Map getWhitelistedCompatModePackages(String setting) {
+ if (TextUtils.isEmpty(setting)) {
return null;
}
- final Set compatPackages = new ArraySet<>();
+
+ final ArrayMap compatPackages = new ArrayMap<>();
final SimpleStringSplitter splitter = new SimpleStringSplitter(COMPAT_PACKAGE_DELIMITER);
- splitter.setString(compatPackagesSetting);
+ splitter.setString(setting);
while (splitter.hasNext()) {
- compatPackages.add(splitter.next());
+ final String packageBlock = splitter.next();
+ final int urlBlockIndex = packageBlock.indexOf(COMPAT_PACKAGE_URL_IDS_BLOCK_BEGIN);
+ final String packageName;
+ final List urlBarIds;
+ if (urlBlockIndex == -1) {
+ packageName = packageBlock;
+ urlBarIds = null;
+ } else {
+ if (packageBlock.charAt(packageBlock.length() - 1)
+ != COMPAT_PACKAGE_URL_IDS_BLOCK_END) {
+ Slog.w(TAG, "Ignoring entry '" + packageBlock + "' on '" + setting
+ + "'because it does not end on '" + COMPAT_PACKAGE_URL_IDS_BLOCK_END +
+ "'");
+ continue;
+ }
+ packageName = packageBlock.substring(0, urlBlockIndex);
+ urlBarIds = new ArrayList<>();
+ final String urlBarIdsBlock =
+ packageBlock.substring(urlBlockIndex + 1, packageBlock.length() - 1);
+ if (sVerbose) {
+ Slog.v(TAG, "pkg:" + packageName + ": block:" + packageBlock + ": urls:"
+ + urlBarIds + ": block:" + urlBarIdsBlock + ":");
+ }
+ final SimpleStringSplitter splitter2 =
+ new SimpleStringSplitter(COMPAT_PACKAGE_URL_IDS_DELIMITER);
+ splitter2.setString(urlBarIdsBlock);
+ while (splitter2.hasNext()) {
+ final String urlBarId = splitter2.next();
+ urlBarIds.add(urlBarId);
+ }
+ }
+ if (urlBarIds == null) {
+ compatPackages.put(packageName, null);
+ } else {
+ final String[] urlBarIdsArray = new String[urlBarIds.size()];
+ urlBarIds.toArray(urlBarIdsArray);
+ compatPackages.put(packageName, urlBarIdsArray);
+ }
}
return compatPackages;
}
@@ -598,13 +645,41 @@ public final class AutofillManagerService extends SystemService {
return mAutofillCompatState.isCompatibilityModeRequested(
packageName, versionCode, userId);
}
+
}
- private static final class AutofillCompatState {
+ /**
+ * Compatibility mode metadata per package.
+ */
+ private static final class PackageCompatState {
+ private final long maxVersionCode;
+ private final String[] urlBarResourceIds;
+
+ PackageCompatState(long maxVersionCode, String[] urlBarResourceIds) {
+ this.maxVersionCode = maxVersionCode;
+ this.urlBarResourceIds = urlBarResourceIds;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageCompatState: [maxVersionCode=" + maxVersionCode
+ + ", urlBarResourceIds=" + Arrays.toString(urlBarResourceIds) + "]";
+ }
+ }
+
+ /**
+ * Compatibility mode metadata associated with all services.
+ *
+ * This object is defined here instead of on each {@link AutofillManagerServiceImpl} because
+ * it cannot hold a lock on the main lock when
+ * {@link AutofillCompatState#isCompatibilityModeRequested(String, long, int)} is called by
+ * external services.
+ */
+ static final class AutofillCompatState {
private final Object mLock = new Object();
@GuardedBy("mLock")
- private SparseArray> mUserSpecs;
+ private SparseArray> mUserSpecs;
boolean isCompatibilityModeRequested(@NonNull String packageName,
long versionCode, @UserIdInt int userId) {
@@ -612,30 +687,49 @@ public final class AutofillManagerService extends SystemService {
if (mUserSpecs == null) {
return false;
}
- final ArrayMap userSpec = mUserSpecs.get(userId);
+ final ArrayMap userSpec = mUserSpecs.get(userId);
if (userSpec == null) {
return false;
}
- final Long maxVersionCode = userSpec.get(packageName);
- if (maxVersionCode == null) {
+ final PackageCompatState metadata = userSpec.get(packageName);
+ if (metadata == null) {
return false;
}
- return versionCode <= maxVersionCode;
+ return versionCode <= metadata.maxVersionCode;
+ }
+ }
+
+ @Nullable
+ String[] getUrlBarResourceIds(@NonNull String packageName, @UserIdInt int userId) {
+ synchronized (mLock) {
+ if (mUserSpecs == null) {
+ return null;
+ }
+ final ArrayMap userSpec = mUserSpecs.get(userId);
+ if (userSpec == null) {
+ return null;
+ }
+ final PackageCompatState metadata = userSpec.get(packageName);
+ if (metadata == null) {
+ return null;
+ }
+ return metadata.urlBarResourceIds;
}
}
void addCompatibilityModeRequest(@NonNull String packageName,
- long versionCode, @UserIdInt int userId) {
+ long versionCode, @Nullable String[] urlBarResourceIds, @UserIdInt int userId) {
synchronized (mLock) {
if (mUserSpecs == null) {
mUserSpecs = new SparseArray<>();
}
- ArrayMap userSpec = mUserSpecs.get(userId);
+ ArrayMap userSpec = mUserSpecs.get(userId);
if (userSpec == null) {
userSpec = new ArrayMap<>();
mUserSpecs.put(userId, userSpec);
}
- userSpec.put(packageName, versionCode);
+ userSpec.put(packageName,
+ new PackageCompatState(versionCode, urlBarResourceIds));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 8622dbe820a68..54ea3ba4546b5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -64,7 +64,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.LocalLog;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -78,12 +77,12 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
+import com.android.server.autofill.AutofillManagerService.AutofillCompatState;
import com.android.server.autofill.ui.AutoFillUI;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
import java.util.Random;
/**
@@ -164,12 +163,15 @@ final class AutofillManagerServiceImpl {
@GuardedBy("mLock")
private FillEventHistory mEventHistory;
+ /** Shared instance, doesn't need to be logged */
+ private final AutofillCompatState mAutofillCompatState;
+
/** When was {@link PruneTask} last executed? */
private long mLastPrune = 0;
AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
- boolean disabled) {
+ AutofillCompatState autofillCompatState, boolean disabled) {
mContext = context;
mLock = lock;
mRequestsHistory = requestsHistory;
@@ -178,6 +180,7 @@ final class AutofillManagerServiceImpl {
mUserId = userId;
mUi = ui;
mFieldClassificationStrategy = new FieldClassificationStrategy(context, userId);
+ mAutofillCompatState = autofillCompatState;
updateLocked(disabled);
}
@@ -208,14 +211,9 @@ final class AutofillManagerServiceImpl {
}
- @GuardedBy("mLock")
@Nullable
- String getUrlBarResourceIdForCompatModeLocked(@NonNull String packageName) {
- if (mInfo == null) {
- Slog.w(TAG, "getUrlBarResourceIdForCompatModeLocked(): no mInfo");
- return null;
- }
- return mInfo.getUrlBarResourceId(packageName);
+ String[] getUrlBarResourceIdsForCompatMode(@NonNull String packageName) {
+ return mAutofillCompatState.getUrlBarResourceIds(packageName, mUserId);
}
@Nullable
@@ -893,7 +891,7 @@ final class AutofillManagerServiceImpl {
pw.print(prefix); pw.print("Field classification enabled: ");
pw.println(isFieldClassificationEnabledLocked());
pw.print(prefix); pw.print("Compat pkgs: ");
- final ArrayMap> compatPkgs = getCompatibilityPackagesLocked();
+ final ArrayMap compatPkgs = getCompatibilityPackagesLocked();
if (compatPkgs == null) {
pw.println("N/A");
} else {
@@ -1022,7 +1020,7 @@ final class AutofillManagerServiceImpl {
}
@GuardedBy("mLock")
- @Nullable ArrayMap> getCompatibilityPackagesLocked() {
+ @Nullable ArrayMap getCompatibilityPackagesLocked() {
if (mInfo != null) {
return mInfo.getCompatibilityPackages();
}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 232dfdcf045bd..5c41f3f869cef 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -30,6 +30,7 @@ import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -168,21 +169,24 @@ public final class Helper {
/**
* Sanitize the {@code webDomain} property of the URL bar node on compat mode.
+ *
+ * @param structure Assist structure
+ * @param urlBarIds list of ids; only the first id found will be sanitized.
*/
public static void sanitizeUrlBar(@NonNull AssistStructure structure,
- @NonNull String urlBarId) {
+ @NonNull String[] urlBarIds) {
final ViewNode urlBarNode = findViewNode(structure, (node) -> {
- return urlBarId.equals(node.getIdEntry());
+ return ArrayUtils.contains(urlBarIds, node.getIdEntry());
});
if (urlBarNode != null) {
final String domain = urlBarNode.getText().toString();
if (domain.isEmpty()) {
- if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarId);
+ if (sDebug) Slog.d(TAG, "sanitizeUrlBar(): empty on " + urlBarNode.getIdEntry());
return;
}
urlBarNode.setWebDomain(domain);
if (sDebug) {
- Slog.d(TAG, "sanitizeUrlBar(): id=" + urlBarId + ", domain="
+ Slog.d(TAG, "sanitizeUrlBar(): id=" + urlBarNode.getIdEntry() + ", domain="
+ urlBarNode.getWebDomain());
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4abf0f8e8f1ab..26598a2e24bc9 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -274,11 +274,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
if (mCompatMode) {
// Sanitize URL bar, if needed
- final String urlBarId = mService.getUrlBarResourceIdForCompatModeLocked(
+ final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode(
mComponentName.getPackageName());
- if (sDebug) Slog.d(TAG, "url_bar in compat mode: " + urlBarId);
- if (urlBarId != null) {
- Helper.sanitizeUrlBar(structure, urlBarId);
+ if (sDebug) {
+ Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds));
+ }
+ if (urlBarIds != null) {
+ Helper.sanitizeUrlBar(structure, urlBarIds);
}
}
structure.sanitizeForParceling(true);
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 356e64b5dd7bd..0ca0a1a104c8f 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -15,6 +15,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
frameworks-base-testutils \
services.accessibility \
services.appwidget \
+ services.autofill \
services.backup \
services.core \
services.devicepolicy \
diff --git a/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java
new file mode 100644
index 0000000000000..d5a28f6b1d76f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/AutofillManagerServiceTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 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.autofill;
+
+import static com.android.server.autofill.AutofillManagerService.getWhitelistedCompatModePackages;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class AutofillManagerServiceTest {
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_null() {
+ assertThat(getWhitelistedCompatModePackages(null)).isNull();
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_empty() {
+ assertThat(getWhitelistedCompatModePackages("")).isNull();
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_onePackageNoUrls() {
+ assertThat(getWhitelistedCompatModePackages("one_is_the_loniest_package"))
+ .containsExactly("one_is_the_loniest_package", null);
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_onePackageMissingEndDelimiter() {
+ assertThat(getWhitelistedCompatModePackages("one_is_the_loniest_package[")).isEmpty();
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_onePackageOneUrl() {
+ final Map result =
+ getWhitelistedCompatModePackages("one_is_the_loniest_package[url]");
+ assertThat(result).hasSize(1);
+ assertThat(result.get("one_is_the_loniest_package")).asList().containsExactly("url");
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_onePackageMultipleUrls() {
+ final Map result =
+ getWhitelistedCompatModePackages("one_is_the_loniest_package[4,5,8,15,16,23,42]");
+ assertThat(result).hasSize(1);
+ assertThat(result.get("one_is_the_loniest_package")).asList()
+ .containsExactly("4", "5", "8", "15", "16", "23", "42");
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_multiplePackagesOneInvalid() {
+ final Map result = getWhitelistedCompatModePackages("one:two[");
+ assertThat(result).hasSize(1);
+ assertThat(result.get("one")).isNull();
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_multiplePackagesMultipleUrls() {
+ final Map result =
+ getWhitelistedCompatModePackages("p1[p1u1]:p2:p3[p3u1,p3u2]");
+ assertThat(result).hasSize(3);
+ assertThat(result.get("p1")).asList().containsExactly("p1u1");
+ assertThat(result.get("p2")).isNull();
+ assertThat(result.get("p3")).asList().containsExactly("p3u1", "p3u2");
+ }
+
+ @Test
+ public void testGetWhitelistedCompatModePackages_threePackagesOneInvalid() {
+ final Map result =
+ getWhitelistedCompatModePackages("p1[p1u1]:p2[:p3[p3u1,p3u2]");
+ assertThat(result).hasSize(2);
+ assertThat(result.get("p1")).asList().containsExactly("p1u1");
+ assertThat(result.get("p3")).asList().containsExactly("p3u1", "p3u2");
+ }
+}