blob: 3f9a57e078768f942bf342f97c9fad960ed98cf4 [file] [log] [blame]
/*
* 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++;
}
}
}