summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author MÃ¥rten Kongstad <marten.kongstad@sony.com> 2018-05-03 10:48:35 +0200
committer Todd Kennedy <toddke@google.com> 2019-04-09 06:44:34 -0700
commit1370c0590ca57cdff5f7494e86b5585c016846a9 (patch)
tree3f42e256126a45ed6013fe30eda09351d0801617
parentdd07ae579c291a2b6ffe09bd576fd908eb9e5ddd (diff)
OMS: add internal tests
Add unit tests for OMS internal components. These are different from the OMS public API tests [that reside in core/tests/overlaytest]. Also, fix typo in OverlayInfo#toString. Bug: 119443459 Test: atest FrameworksServicesTests:com.android.server.om Change-Id: Icbee70800de5157a1804e0c96101f31c0bad2619
-rw-r--r--core/java/android/content/om/OverlayInfo.java2
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java7
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerSettings.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java504
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java505
5 files changed, 1022 insertions, 3 deletions
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index fc79a425e861..639335e53ae5 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -451,7 +451,7 @@ public final class OverlayInfo implements Parcelable {
public String toString() {
return "OverlayInfo { overlay=" + packageName + ", targetPackage=" + targetPackageName
+ ((targetOverlayableName == null) ? ""
- : ", targetOverlyabale=" + targetOverlayableName)
+ : ", targetOverlayable=" + targetOverlayableName)
+ ", state=" + state + " (" + stateToString(state) + "), userId=" + userId + " }";
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 51d5acc9b555..ee07c7de9dbc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -166,6 +166,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
* . . . . . . . . . . . . . . . . . . . . . .
* </pre>
*
+ * <p>To test the OMS, execute:
+ * <code>
+ * atest FrameworksServicesTests:com.android.server.om # internal tests
+ * atest OverlayDeviceTests OverlayHostTests # public API tests
+ * </code>
+ * </p>
+ *
* <p>Finally, here is a list of keywords used in the OMS context.</p>
*
* <ul>
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 36b5beb7bbb2..f35c70780db9 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -26,6 +26,7 @@ import android.util.ArrayMap;
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -327,7 +328,8 @@ final class OverlayManagerSettings {
Serializer.persist(mItems, os);
}
- private static final class Serializer {
+ @VisibleForTesting
+ static final class Serializer {
private static final String TAG_OVERLAYS = "overlays";
private static final String TAG_ITEM = "item";
@@ -343,7 +345,8 @@ final class OverlayManagerSettings {
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_VERSION = "version";
- private static final int CURRENT_VERSION = 3;
+ @VisibleForTesting
+ static final int CURRENT_VERSION = 3;
public static void restore(@NonNull final ArrayList<SettingsItem> table,
@NonNull final InputStream is) throws IOException, XmlPullParserException {
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
new file mode 100644
index 000000000000..3f9a57e07876
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2019 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.om;
+
+import static android.content.om.OverlayInfo.STATE_DISABLED;
+import static android.content.om.OverlayInfo.STATE_ENABLED;
+import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
+import static android.content.om.OverlayInfo.STATE_TARGET_IS_BEING_REPLACED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.content.om.OverlayInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayManagerServiceImplTests {
+ private OverlayManagerServiceImpl mImpl;
+ private DummyDeviceState mState;
+ private DummyListener mListener;
+
+ private static final String OVERLAY = "com.dummy.overlay";
+ private static final String TARGET = "com.dummy.target";
+ private static final int USER = 0;
+
+ private static final String OVERLAY2 = OVERLAY + "2";
+ private static final String TARGET2 = TARGET + "2";
+ private static final int USER2 = USER + 1;
+
+ private static final String OVERLAY3 = OVERLAY + "3";
+ private static final int USER3 = USER2 + 1;
+
+
+ @Before
+ public void setUp() throws Exception {
+ mState = new DummyDeviceState();
+ mListener = new DummyListener();
+ DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState);
+ mImpl = new OverlayManagerServiceImpl(pmh,
+ new DummyIdmapManager(mState, pmh),
+ new OverlayManagerSettings(),
+ new String[0],
+ mListener);
+ }
+
+ // tests: basics
+
+ @Test
+ public void testGetOverlayInfo() throws Exception {
+ installOverlayPackage(OVERLAY, TARGET, USER, false);
+ final OverlayInfo oi = mImpl.getOverlayInfo(OVERLAY, USER);
+ assertNotNull(oi);
+ assertEquals(oi.packageName, OVERLAY);
+ assertEquals(oi.targetPackageName, TARGET);
+ assertEquals(oi.userId, USER);
+ }
+
+ @Test
+ public void testGetOverlayInfosForTarget() throws Exception {
+ installOverlayPackage(OVERLAY, TARGET, USER, false);
+ installOverlayPackage(OVERLAY2, TARGET, USER, false);
+
+ installOverlayPackage(OVERLAY3, TARGET, USER2, false);
+
+ final List<OverlayInfo> ois = mImpl.getOverlayInfosForTarget(TARGET, USER);
+ assertEquals(ois.size(), 2);
+ assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY, USER)));
+ assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY2, USER)));
+
+ final List<OverlayInfo> ois2 = mImpl.getOverlayInfosForTarget(TARGET, USER2);
+ assertEquals(ois2.size(), 1);
+ assertTrue(ois2.contains(mImpl.getOverlayInfo(OVERLAY3, USER2)));
+
+ final List<OverlayInfo> ois3 = mImpl.getOverlayInfosForTarget(TARGET, USER3);
+ assertNotNull(ois3);
+ assertEquals(ois3.size(), 0);
+
+ final List<OverlayInfo> ois4 = mImpl.getOverlayInfosForTarget("no.such.overlay", USER);
+ assertNotNull(ois4);
+ assertEquals(ois4.size(), 0);
+ }
+
+ @Test
+ public void testGetOverlayInfosForUser() throws Exception {
+ installOverlayPackage(OVERLAY, TARGET, USER, false);
+ installOverlayPackage(OVERLAY2, TARGET, USER, false);
+ installOverlayPackage(OVERLAY3, TARGET2, USER, false);
+
+ final Map<String, List<OverlayInfo>> everything = mImpl.getOverlaysForUser(USER);
+ assertEquals(everything.size(), 2);
+
+ final List<OverlayInfo> ois = everything.get(TARGET);
+ assertNotNull(ois);
+ assertEquals(ois.size(), 2);
+ assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY, USER)));
+ assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY2, USER)));
+
+ final List<OverlayInfo> ois2 = everything.get(TARGET2);
+ assertNotNull(ois2);
+ assertEquals(ois2.size(), 1);
+ assertTrue(ois2.contains(mImpl.getOverlayInfo(OVERLAY3, USER)));
+
+ final Map<String, List<OverlayInfo>> everything2 = mImpl.getOverlaysForUser(USER2);
+ assertNotNull(everything2);
+ assertEquals(everything2.size(), 0);
+ }
+
+ @Test
+ public void testPriority() throws Exception {
+ installOverlayPackage(OVERLAY, TARGET, USER, false);
+ installOverlayPackage(OVERLAY2, TARGET, USER, false);
+ installOverlayPackage(OVERLAY3, TARGET, USER, false);
+
+ final OverlayInfo o1 = mImpl.getOverlayInfo(OVERLAY, USER);
+ final OverlayInfo o2 = mImpl.getOverlayInfo(OVERLAY2, USER);
+ final OverlayInfo o3 = mImpl.getOverlayInfo(OVERLAY3, USER);
+
+ assertOverlayInfoList(TARGET, USER, o1, o2, o3);
+
+ assertTrue(mImpl.setLowestPriority(OVERLAY3, USER));
+ assertOverlayInfoList(TARGET, USER, o3, o1, o2);
+
+ assertTrue(mImpl.setHighestPriority(OVERLAY3, USER));
+ assertOverlayInfoList(TARGET, USER, o1, o2, o3);
+
+ assertTrue(mImpl.setPriority(OVERLAY, OVERLAY2, USER));
+ assertOverlayInfoList(TARGET, USER, o2, o1, o3);
+ }
+
+ @Test
+ public void testOverlayInfoStateTransitions() throws Exception {
+ assertNull(mImpl.getOverlayInfo(OVERLAY, USER));
+
+ installOverlayPackage(OVERLAY, TARGET, USER, true);
+ assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+
+ installTargetPackage(TARGET, USER);
+ assertState(STATE_DISABLED, OVERLAY, USER);
+
+ mImpl.setEnabled(OVERLAY, true, USER);
+ assertState(STATE_ENABLED, OVERLAY, USER);
+
+ beginUpgradeTargetPackage(TARGET, USER);
+ assertState(STATE_TARGET_IS_BEING_REPLACED, OVERLAY, USER);
+
+ endUpgradeTargetPackage(TARGET, USER);
+ assertState(STATE_ENABLED, OVERLAY, USER);
+
+ uninstallTargetPackage(TARGET, USER);
+ assertState(STATE_MISSING_TARGET, OVERLAY, USER);
+
+ installTargetPackage(TARGET, USER);
+ assertState(STATE_ENABLED, OVERLAY, USER);
+ }
+
+ @Test
+ public void testUpdateOverlaysForUser() throws Exception {
+ installTargetPackage(TARGET, USER);
+ installTargetPackage("some.other.target", USER);
+ installOverlayPackage(OVERLAY, TARGET, USER, true);
+
+ // do nothing, expect no change
+ List<String> a = mImpl.updateOverlaysForUser(USER);
+ assertEquals(1, a.size());
+ assertTrue(a.contains(TARGET));
+
+ // upgrade overlay, keep target
+ upgradeOverlayPackage(OVERLAY, TARGET, USER, true);
+ List<String> b = mImpl.updateOverlaysForUser(USER);
+ assertEquals(1, b.size());
+ assertTrue(b.contains(TARGET));
+
+ // do nothing, expect no change
+ List<String> c = mImpl.updateOverlaysForUser(USER);
+ assertEquals(1, c.size());
+ assertTrue(c.contains(TARGET));
+
+ // upgrade overlay, switch to new target
+ upgradeOverlayPackage(OVERLAY, "some.other.target", USER, true);
+ List<String> d = mImpl.updateOverlaysForUser(USER);
+ assertEquals(2, d.size());
+ assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
+
+ // do nothing, expect no change
+ List<String> e = mImpl.updateOverlaysForUser(USER);
+ assertEquals(1, e.size());
+ assertTrue(e.contains("some.other.target"));
+ }
+
+ @Test
+ public void testOnOverlayPackageUpgraded() throws Exception {
+ installTargetPackage(TARGET, USER);
+ installOverlayPackage(OVERLAY, TARGET, USER, true);
+ mImpl.onOverlayPackageReplacing(OVERLAY, USER);
+ mListener.count = 0;
+ mImpl.onOverlayPackageReplaced(OVERLAY, USER);
+ assertEquals(1, mListener.count);
+
+ // upgrade to a version where the overlay has changed its target
+ upgradeOverlayPackage(OVERLAY, "some.other.target", USER, true);
+ mImpl.onOverlayPackageReplacing(OVERLAY, USER);
+ mListener.count = 0;
+ mImpl.onOverlayPackageReplaced(OVERLAY, USER);
+ // expect once for the old target package, once for the new target package
+ assertEquals(2, mListener.count);
+
+ upgradeOverlayPackage(OVERLAY, "some.other.target", USER, true);
+ mImpl.onOverlayPackageReplacing(OVERLAY, USER);
+ mListener.count = 0;
+ mImpl.onOverlayPackageReplaced(OVERLAY, USER);
+ assertEquals(1, mListener.count);
+ }
+
+ // tests: listener interface
+
+ @Test
+ public void testListener() throws Exception {
+ installOverlayPackage(OVERLAY, TARGET, USER, true);
+ assertEquals(1, mListener.count);
+ mListener.count = 0;
+
+ installTargetPackage(TARGET, USER);
+ assertEquals(1, mListener.count);
+ mListener.count = 0;
+
+ mImpl.setEnabled(OVERLAY, true, USER);
+ assertEquals(1, mListener.count);
+ mListener.count = 0;
+
+ mImpl.setEnabled(OVERLAY, true, USER);
+ assertEquals(0, mListener.count);
+ }
+
+ // helper methods
+
+ private void assertState(int expected, final String overlayPackageName, int userId) {
+ int actual = mImpl.getOverlayInfo(OVERLAY, USER).state;
+ String msg = String.format("expected %s but was %s:",
+ OverlayInfo.stateToString(expected), OverlayInfo.stateToString(actual));
+ assertEquals(msg, expected, actual);
+ }
+
+ private void assertOverlayInfoList(final String targetPackageName, int userId,
+ OverlayInfo... overlayInfos) {
+ final List<OverlayInfo> expected =
+ mImpl.getOverlayInfosForTarget(targetPackageName, userId);
+ final List<OverlayInfo> actual = Arrays.asList(overlayInfos);
+ assertEquals(expected, actual);
+ }
+
+ private void installTargetPackage(String packageName, int userId) {
+ if (mState.select(packageName, userId) != null) {
+ throw new IllegalStateException("package already installed");
+ }
+ mState.add(packageName, null, userId, false);
+ mImpl.onTargetPackageAdded(packageName, userId);
+ }
+
+ private void beginUpgradeTargetPackage(String packageName, int userId) {
+ if (mState.select(packageName, userId) == null) {
+ throw new IllegalStateException("package not installed");
+ }
+ mState.add(packageName, null, userId, false);
+ mImpl.onTargetPackageReplacing(packageName, userId);
+ }
+
+ private void endUpgradeTargetPackage(String packageName, int userId) {
+ if (mState.select(packageName, userId) == null) {
+ throw new IllegalStateException("package not installed");
+ }
+ mState.add(packageName, null, userId, false);
+ mImpl.onTargetPackageReplaced(packageName, userId);
+ }
+
+ private void uninstallTargetPackage(String packageName, int userId) {
+ if (mState.select(packageName, userId) == null) {
+ throw new IllegalStateException("package not installed");
+ }
+ mState.remove(packageName, userId);
+ mImpl.onTargetPackageRemoved(packageName, userId);
+ }
+
+ private void installOverlayPackage(String packageName, String targetPackageName, int userId,
+ boolean canCreateIdmap) {
+ if (mState.select(packageName, userId) != null) {
+ throw new IllegalStateException("package already installed");
+ }
+ mState.add(packageName, targetPackageName, userId, canCreateIdmap);
+ mImpl.onOverlayPackageAdded(packageName, userId);
+ }
+
+ private void upgradeOverlayPackage(String packageName, String targetPackageName, int userId,
+ boolean canCreateIdmap) {
+ DummyDeviceState.Package pkg = mState.select(packageName, userId);
+ if (pkg == null) {
+ throw new IllegalStateException("package not installed, cannot upgrade");
+ }
+ pkg.targetPackageName = targetPackageName;
+ pkg.canCreateIdmap = canCreateIdmap;
+ }
+
+ private void uninstallOverlayPackage(String packageName, int userId) {
+ // implement this when adding support for downloadable overlays
+ throw new IllegalArgumentException("not implemented");
+ }
+
+ private static final class DummyDeviceState {
+ private List<Package> mPackages = new ArrayList<>();
+
+ public void add(String packageName, String targetPackageName, int userId,
+ boolean canCreateIdmap) {
+ remove(packageName, userId);
+ Package pkg = new Package();
+ pkg.packageName = packageName;
+ pkg.targetPackageName = targetPackageName;
+ pkg.userId = userId;
+ pkg.canCreateIdmap = canCreateIdmap;
+ mPackages.add(pkg);
+ }
+
+ public void remove(String packageName, int userId) {
+ final Iterator<Package> iter = mPackages.iterator();
+ while (iter.hasNext()) {
+ final Package pkg = iter.next();
+ if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
+ iter.remove();
+ return;
+ }
+ }
+ }
+
+ public List<Package> select(int userId) {
+ List<Package> out = new ArrayList<>();
+ final int packageCount = mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ final Package pkg = mPackages.get(i);
+ if (pkg.userId == userId) {
+ out.add(pkg);
+ }
+ }
+ return out;
+ }
+
+ public Package select(String packageName, int userId) {
+ final int packageCount = mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ final Package pkg = mPackages.get(i);
+ if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
+ return pkg;
+ }
+ }
+ return null;
+ }
+
+ private static final class Package {
+ public String packageName;
+ public int userId;
+ public String targetPackageName;
+ public boolean canCreateIdmap;
+ }
+ }
+
+ private static final class DummyPackageManagerHelper implements
+ OverlayManagerServiceImpl.PackageManagerHelper {
+ private final DummyDeviceState mState;
+
+ DummyPackageManagerHelper(DummyDeviceState state) {
+ mState = state;
+ }
+
+ @Override
+ public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
+ final DummyDeviceState.Package pkg = mState.select(packageName, userId);
+ if (pkg == null) {
+ return null;
+ }
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.sourceDir = String.format("%s/%s/base.apk",
+ pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/",
+ pkg.packageName);
+ PackageInfo pi = new PackageInfo();
+ pi.applicationInfo = ai;
+ pi.packageName = pkg.packageName;
+ pi.overlayTarget = pkg.targetPackageName;
+ pi.overlayCategory = "dummy-category-" + pkg.targetPackageName;
+ return pi;
+ }
+
+ @Override
+ public boolean signaturesMatching(@NonNull String packageName1,
+ @NonNull String packageName2, int userId) {
+ return false;
+ }
+
+ @Override
+ public List<PackageInfo> getOverlayPackages(int userId) {
+ List<PackageInfo> out = new ArrayList<>();
+ final List<DummyDeviceState.Package> packages = mState.select(userId);
+ final int packageCount = packages.size();
+ for (int i = 0; i < packageCount; i++) {
+ final DummyDeviceState.Package pkg = packages.get(i);
+ if (pkg.targetPackageName != null) {
+ out.add(getPackageInfo(pkg.packageName, pkg.userId));
+ }
+ }
+ return out;
+ }
+ }
+
+ private static class DummyIdmapManager extends IdmapManager {
+ private final DummyDeviceState mState;
+ private Set<String> mIdmapFiles = new ArraySet<>();
+
+ DummyIdmapManager(DummyDeviceState state, DummyPackageManagerHelper packageManagerHelper) {
+ super(null, packageManagerHelper);
+ mState = state;
+ }
+
+ @Override
+ boolean createIdmap(@NonNull final PackageInfo targetPackage,
+ @NonNull final PackageInfo overlayPackage, int userId) {
+ final DummyDeviceState.Package t = mState.select(targetPackage.packageName, userId);
+ if (t == null) {
+ return false;
+ }
+ final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId);
+ if (o == null) {
+ return false;
+ }
+ if (!o.canCreateIdmap) {
+ return false;
+ }
+ final String key = createKey(overlayPackage.packageName, userId);
+ mIdmapFiles.add(key);
+ return true;
+ }
+
+ @Override
+ boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
+ final String key = createKey(oi.packageName, oi.userId);
+ if (!mIdmapFiles.contains(key)) {
+ return false;
+ }
+ mIdmapFiles.remove(key);
+ return true;
+ }
+
+ @Override
+ boolean idmapExists(@NonNull final OverlayInfo oi) {
+ final String key = createKey(oi.packageName, oi.userId);
+ return mIdmapFiles.contains(key);
+ }
+
+ @Override
+ boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
+ final String key = createKey(overlayPackage.packageName, userId);
+ return mIdmapFiles.contains(key);
+ }
+
+ private String createKey(@NonNull final String packageName, final int userId) {
+ return String.format("%s:%d", packageName, userId);
+ }
+ }
+
+ private static class DummyListener implements OverlayManagerServiceImpl.OverlayChangeListener {
+ public int count;
+
+ public void onOverlaysChanged(@NonNull String targetPackage, int userId) {
+ count++;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
new file mode 100644
index 000000000000..8ff8b6e4a9e0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2019 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.om;
+
+import static android.content.om.OverlayInfo.STATE_DISABLED;
+import static android.content.om.OverlayInfo.STATE_ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.om.OverlayInfo;
+import android.text.TextUtils;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayManagerSettingsTests {
+ private OverlayManagerSettings mSettings;
+
+ private static final OverlayInfo OVERLAY_A0 = new OverlayInfo(
+ "com.dummy.overlay_a",
+ "com.dummy.target",
+ null,
+ "some-category",
+ "/data/app/com.dummy.overlay_a-1/base.apk",
+ STATE_DISABLED,
+ 0,
+ 0,
+ false);
+
+ private static final OverlayInfo OVERLAY_B0 = new OverlayInfo(
+ "com.dummy.overlay_b",
+ "com.dummy.target",
+ null,
+ "some-category",
+ "/data/app/com.dummy.overlay_b-1/base.apk",
+ STATE_DISABLED,
+ 0,
+ 0,
+ false);
+
+ private static final OverlayInfo OVERLAY_C0 = new OverlayInfo(
+ "com.dummy.overlay_c",
+ "com.dummy.target",
+ null,
+ "some-category",
+ "/data/app/com.dummy.overlay_c-1/base.apk",
+ STATE_DISABLED,
+ 0,
+ 0,
+ false);
+
+ private static final OverlayInfo OVERLAY_A1 = new OverlayInfo(
+ "com.dummy.overlay_a",
+ "com.dummy.target",
+ null,
+ "some-category",
+ "/data/app/com.dummy.overlay_a-1/base.apk",
+ STATE_DISABLED,
+ 1,
+ 0,
+ false);
+
+ private static final OverlayInfo OVERLAY_B1 = new OverlayInfo(
+ "com.dummy.overlay_b",
+ "com.dummy.target",
+ null,
+ "some-category",
+ "/data/app/com.dummy.overlay_b-1/base.apk",
+ STATE_DISABLED,
+ 1,
+ 0,
+ false);
+
+ @Before
+ public void setUp() throws Exception {
+ mSettings = new OverlayManagerSettings();
+ }
+
+ // tests: generic functionality
+
+ @Test
+ public void testSettingsInitiallyEmpty() throws Exception {
+ final int userId = 0;
+ Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(userId);
+ assertEquals(0, map.size());
+ }
+
+ @Test
+ public void testBasicSetAndGet() throws Exception {
+ assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+
+ insert(OVERLAY_A0);
+ assertContains(mSettings, OVERLAY_A0);
+ OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
+ assertEquals(OVERLAY_A0, oi);
+
+ assertTrue(mSettings.remove(OVERLAY_A0.packageName, OVERLAY_A0.userId));
+ assertDoesNotContain(mSettings, OVERLAY_A0.packageName, OVERLAY_A0.userId);
+ }
+
+ @Test
+ public void testGetUsers() throws Exception {
+ int[] users = mSettings.getUsers();
+ assertEquals(0, users.length);
+
+ insert(OVERLAY_A0);
+ users = mSettings.getUsers();
+ assertEquals(1, users.length);
+ assertContains(users, OVERLAY_A0.userId);
+
+ insert(OVERLAY_A1);
+ insert(OVERLAY_B1);
+ users = mSettings.getUsers();
+ assertEquals(2, users.length);
+ assertContains(users, OVERLAY_A0.userId);
+ assertContains(users, OVERLAY_A1.userId);
+ }
+
+ @Test
+ public void testGetOverlaysForUser() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+ insert(OVERLAY_A1);
+ insert(OVERLAY_B1);
+
+ Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(OVERLAY_A0.userId);
+ assertEquals(1, map.keySet().size());
+ assertTrue(map.keySet().contains(OVERLAY_A0.targetPackageName));
+
+ List<OverlayInfo> list = map.get(OVERLAY_A0.targetPackageName);
+ assertEquals(2, list.size());
+ assertTrue(list.contains(OVERLAY_A0));
+ assertTrue(list.contains(OVERLAY_B0));
+
+ // getOverlaysForUser should never return null
+ map = mSettings.getOverlaysForUser(-1);
+ assertNotNull(map);
+ assertEquals(0, map.size());
+ }
+
+ @Test
+ public void testRemoveUser() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+ insert(OVERLAY_A1);
+
+ assertContains(mSettings, OVERLAY_A0);
+ assertContains(mSettings, OVERLAY_B0);
+ assertContains(mSettings, OVERLAY_A1);
+
+ mSettings.removeUser(OVERLAY_A0.userId);
+
+ assertDoesNotContain(mSettings, OVERLAY_A0);
+ assertDoesNotContain(mSettings, OVERLAY_B0);
+ assertContains(mSettings, OVERLAY_A1);
+ }
+
+ @Test
+ public void testOrderOfNewlyAddedItems() throws Exception {
+ // new items are appended to the list
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+ insert(OVERLAY_C0);
+
+ List<OverlayInfo> list =
+ mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+
+ // overlays keep their positions when updated
+ mSettings.setState(OVERLAY_B0.packageName, OVERLAY_B0.userId, STATE_ENABLED);
+ OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B0.packageName, OVERLAY_B0.userId);
+
+ list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, oi, OVERLAY_C0);
+ }
+
+ @Test
+ public void testSetPriority() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+ insert(OVERLAY_C0);
+
+ List<OverlayInfo> list =
+ mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+
+ boolean changed = mSettings.setPriority(OVERLAY_B0.packageName, OVERLAY_C0.packageName,
+ OVERLAY_B0.userId);
+ assertTrue(changed);
+ list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+
+ changed =
+ mSettings.setPriority(OVERLAY_B0.packageName, "does.not.exist", OVERLAY_B0.userId);
+ assertFalse(changed);
+ list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+
+ OverlayInfo otherTarget = new OverlayInfo(
+ "com.dummy.overlay_other",
+ "com.dummy.some.other.target",
+ null,
+ "some-category",
+ "/data/app/com.dummy.overlay_other-1/base.apk",
+ STATE_DISABLED,
+ 0,
+ 0,
+ false);
+ insert(otherTarget);
+ changed = mSettings.setPriority(OVERLAY_A0.packageName, otherTarget.packageName,
+ OVERLAY_A0.userId);
+ assertFalse(changed);
+ }
+
+ @Test
+ public void testSetLowestPriority() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+ insert(OVERLAY_C0);
+
+ List<OverlayInfo> list =
+ mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+
+ boolean changed = mSettings.setLowestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
+ assertTrue(changed);
+
+ list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_B0, OVERLAY_A0, OVERLAY_C0);
+ }
+
+ @Test
+ public void testSetHighestPriority() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+ insert(OVERLAY_C0);
+
+ List<OverlayInfo> list =
+ mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_B0, OVERLAY_C0);
+
+ boolean changed = mSettings.setHighestPriority(OVERLAY_B0.packageName, OVERLAY_B0.userId);
+ assertTrue(changed);
+
+ list = mSettings.getOverlaysForTarget(OVERLAY_A0.targetPackageName, OVERLAY_A0.userId);
+ assertListsAreEqual(list, OVERLAY_A0, OVERLAY_C0, OVERLAY_B0);
+ }
+
+ // tests: persist and restore
+
+ @Test
+ public void testPersistEmpty() throws Exception {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ mSettings.persist(os);
+ String xml = new String(os.toByteArray(), "utf-8");
+
+ assertEquals(1, countXmlTags(xml, "overlays"));
+ assertEquals(0, countXmlTags(xml, "item"));
+ }
+
+ @Test
+ public void testPersistDifferentOverlaysSameUser() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B0);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ mSettings.persist(os);
+ final String xml = new String(os.toByteArray(), "utf-8");
+
+ assertEquals(1, countXmlTags(xml, "overlays"));
+ assertEquals(2, countXmlTags(xml, "item"));
+ assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
+ OVERLAY_A0.packageName));
+ assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
+ OVERLAY_B0.packageName));
+ assertEquals(2, countXmlAttributesWhere(xml, "item", "userId",
+ Integer.toString(OVERLAY_A0.userId)));
+ }
+
+ @Test
+ public void testPersistSameOverlayDifferentUsers() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_A1);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ mSettings.persist(os);
+ String xml = new String(os.toByteArray(), "utf-8");
+
+ assertEquals(1, countXmlTags(xml, "overlays"));
+ assertEquals(2, countXmlTags(xml, "item"));
+ assertEquals(2, countXmlAttributesWhere(xml, "item", "packageName",
+ OVERLAY_A0.packageName));
+ assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
+ Integer.toString(OVERLAY_A0.userId)));
+ assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
+ Integer.toString(OVERLAY_A1.userId)));
+ }
+
+ @Test
+ public void testPersistEnabled() throws Exception {
+ insert(OVERLAY_A0);
+ mSettings.setEnabled(OVERLAY_A0.packageName, OVERLAY_A0.userId, true);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ mSettings.persist(os);
+ String xml = new String(os.toByteArray(), "utf-8");
+
+ assertEquals(1, countXmlAttributesWhere(xml, "item", "isEnabled", "true"));
+ }
+
+ @Test
+ public void testRestoreEmpty() throws Exception {
+ final int version = OverlayManagerSettings.Serializer.CURRENT_VERSION;
+ final String xml =
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<overlays version=\"" + version + "\" />\n";
+ ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
+
+ mSettings.restore(is);
+ assertDoesNotContain(mSettings, "com.dummy.overlay", 0);
+ }
+
+ @Test
+ public void testRestoreSingleUserSingleOverlay() throws Exception {
+ final int version = OverlayManagerSettings.Serializer.CURRENT_VERSION;
+ final String xml =
+ "<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
+ + "<overlays version='" + version + "'>\n"
+ + "<item packageName='com.dummy.overlay'\n"
+ + " userId='1234'\n"
+ + " targetPackageName='com.dummy.target'\n"
+ + " baseCodePath='/data/app/com.dummy.overlay-1/base.apk'\n"
+ + " state='" + STATE_DISABLED + "'\n"
+ + " isEnabled='false'\n"
+ + " category='dummy-category'\n"
+ + " isStatic='false'\n"
+ + " priority='0' />\n"
+ + "</overlays>\n";
+ ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
+
+ mSettings.restore(is);
+ OverlayInfo oi = mSettings.getOverlayInfo("com.dummy.overlay", 1234);
+ assertNotNull(oi);
+ assertEquals("com.dummy.overlay", oi.packageName);
+ assertEquals("com.dummy.target", oi.targetPackageName);
+ assertEquals("/data/app/com.dummy.overlay-1/base.apk", oi.baseCodePath);
+ assertEquals(1234, oi.userId);
+ assertEquals(STATE_DISABLED, oi.state);
+ assertFalse(mSettings.getEnabled("com.dummy.overlay", 1234));
+ }
+
+ @Test
+ public void testPersistAndRestore() throws Exception {
+ insert(OVERLAY_A0);
+ insert(OVERLAY_B1);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ mSettings.persist(os);
+ String xml = new String(os.toByteArray(), "utf-8");
+ ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
+ OverlayManagerSettings newSettings = new OverlayManagerSettings();
+ newSettings.restore(is);
+
+ OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A0.packageName, OVERLAY_A0.userId);
+ assertEquals(OVERLAY_A0, a);
+
+ OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B1.packageName, OVERLAY_B1.userId);
+ assertEquals(OVERLAY_B1, b);
+ }
+
+ private int countXmlTags(String xml, String tagToLookFor) throws Exception {
+ int count = 0;
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new StringReader(xml));
+ int event = parser.getEventType();
+ while (event != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG && tagToLookFor.equals(parser.getName())) {
+ count++;
+ }
+ event = parser.next();
+ }
+ return count;
+ }
+
+ private int countXmlAttributesWhere(String xml, String tag, String attr, String value)
+ throws Exception {
+ int count = 0;
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new StringReader(xml));
+ int event = parser.getEventType();
+ while (event != XmlPullParser.END_DOCUMENT) {
+ if (event == XmlPullParser.START_TAG && tag.equals(parser.getName())) {
+ String v = parser.getAttributeValue(null, attr);
+ if (value.equals(v)) {
+ count++;
+ }
+ }
+ event = parser.next();
+ }
+ return count;
+ }
+
+ private void insert(OverlayInfo oi) throws Exception {
+ mSettings.init(oi.packageName, oi.userId, oi.targetPackageName, null, oi.baseCodePath,
+ false, 0, oi.category);
+ mSettings.setState(oi.packageName, oi.userId, oi.state);
+ mSettings.setEnabled(oi.packageName, oi.userId, false);
+ }
+
+ private static void assertContains(final OverlayManagerSettings settings,
+ final OverlayInfo oi) {
+ assertContains(settings, oi.packageName, oi.userId);
+ }
+
+ private static void assertContains(final OverlayManagerSettings settings,
+ final String packageName, int userId) {
+ try {
+ settings.getOverlayInfo(packageName, userId);
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ fail(String.format("settings does not contain packageName=%s userId=%d",
+ packageName, userId));
+ }
+ }
+
+ private static void assertDoesNotContain(final OverlayManagerSettings settings,
+ final OverlayInfo oi) {
+ assertDoesNotContain(settings, oi.packageName, oi.userId);
+ }
+
+ private static void assertDoesNotContain(final OverlayManagerSettings settings,
+ final String packageName, int userId) {
+ try {
+ settings.getOverlayInfo(packageName, userId);
+ fail(String.format("settings contains packageName=%s userId=%d", packageName, userId));
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ // do nothing: we expect to end up here
+ }
+ }
+
+ private static void assertContains(int[] haystack, int needle) {
+ List<Integer> list = IntStream.of(haystack)
+ .boxed()
+ .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
+ if (!list.contains(needle)) {
+ fail(String.format("integer array [%s] does not contain value %s",
+ TextUtils.join(",", list), needle));
+ }
+ }
+
+ private static void assertDoesNotContain(int[] haystack, int needle) {
+ List<Integer> list = IntStream.of(haystack)
+ .boxed()
+ .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
+ if (list.contains(needle)) {
+ fail(String.format("integer array [%s] contains value %s",
+ TextUtils.join(",", list), needle));
+ }
+ }
+
+ private static void assertListsAreEqual(List<OverlayInfo> list, OverlayInfo... array) {
+ List<OverlayInfo> other = Stream.of(array)
+ .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
+ assertListsAreEqual(list, other);
+ }
+
+ private static void assertListsAreEqual(List<OverlayInfo> list, List<OverlayInfo> other) {
+ if (!list.equals(other)) {
+ fail(String.format("lists [%s] and [%s] differ",
+ TextUtils.join(",", list), TextUtils.join(",", other)));
+ }
+ }
+}