diff options
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))); + } + } +} |