Merge "Add overlayable configurator resources" into rvc-dev

This commit is contained in:
Ryan Mitchell
2020-05-26 17:53:47 +00:00
committed by Android (Google) Code Review
10 changed files with 471 additions and 315 deletions

View File

@@ -4451,4 +4451,11 @@
<bool name="config_pdp_reject_enable_retry">false</bool>
<!-- pdp data reject retry delay in ms -->
<integer name="config_pdp_reject_retry_delay_ms">-1</integer>
<!-- Package name that is recognized as an actor for the packages listed in
@array/config_overlayableConfiguratorTargets. If an overlay targeting one of the listed
targets is signed with the same signature as the configurator, the overlay will be granted
the "actor" policy. -->
<string name="config_overlayableConfigurator" translatable="false" />
<string-array name="config_overlayableConfiguratorTargets" translatable="false" />
</resources>

View File

@@ -4025,4 +4025,6 @@
<java-symbol type="string" name="config_pdp_reject_service_not_subscribed" />
<java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" />
<java-symbol type="string" name="config_overlayableConfigurator" />
<java-symbol type="array" name="config_overlayableConfiguratorTargets" />
</resources>

View File

@@ -30,6 +30,7 @@ import android.util.Slog;
import com.android.server.FgThread;
import java.io.File;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -124,9 +125,13 @@ class IdmapDaemon {
}
}
String getIdmapPath(String overlayPath, int userId) throws TimeoutException, RemoteException {
boolean idmapExists(String overlayPath, int userId) {
try (Connection c = connect()) {
return mService.getIdmapPath(overlayPath, userId);
return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
} catch (Exception e) {
Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
+ e.getMessage());
return false;
}
}

View File

@@ -27,10 +27,10 @@ import android.content.pm.PackageInfo;
import android.os.Build.VERSION_CODES;
import android.os.OverlayablePolicy;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Slog;
import java.io.File;
import com.android.internal.util.ArrayUtils;
import java.io.IOException;
/**
@@ -55,12 +55,16 @@ class IdmapManager {
VENDOR_IS_Q_OR_LATER = isQOrLater;
}
private final OverlayableInfoCallback mOverlayableCallback;
private final IdmapDaemon mIdmapDaemon;
private final OverlayableInfoCallback mOverlayableCallback;
private final String mOverlayableConfigurator;
private final String[] mOverlayableConfiguratorTargets;
IdmapManager(final OverlayableInfoCallback verifyCallback) {
IdmapManager(final IdmapDaemon idmapDaemon, final OverlayableInfoCallback verifyCallback) {
mOverlayableCallback = verifyCallback;
mIdmapDaemon = IdmapDaemon.getInstance();
mIdmapDaemon = idmapDaemon;
mOverlayableConfigurator = verifyCallback.getOverlayableConfigurator();
mOverlayableConfiguratorTargets = verifyCallback.getOverlayableConfiguratorTargets() ;
}
/**
@@ -103,23 +107,11 @@ class IdmapManager {
}
boolean idmapExists(@NonNull final OverlayInfo oi) {
return new File(getIdmapPath(oi.baseCodePath, oi.userId)).isFile();
return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
}
boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath(), userId))
.isFile();
}
private @NonNull String getIdmapPath(@NonNull final String overlayPackagePath,
final int userId) {
try {
return mIdmapDaemon.getIdmapPath(overlayPackagePath, userId);
} catch (Exception e) {
Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": "
+ e.getMessage());
return "";
}
return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
}
/**
@@ -198,9 +190,17 @@ class IdmapManager {
String targetOverlayableName = overlayPackage.targetOverlayableName;
if (targetOverlayableName != null) {
try {
if (!mOverlayableConfigurator.isEmpty()
&& ArrayUtils.contains(mOverlayableConfiguratorTargets,
targetPackage.packageName)
&& mOverlayableCallback.signaturesMatching(mOverlayableConfigurator,
overlayPackage.packageName, userId)) {
return true;
}
OverlayableInfo overlayableInfo = mOverlayableCallback.getOverlayableForTarget(
targetPackage.packageName, targetOverlayableName, userId);
if (overlayableInfo != null) {
if (overlayableInfo != null && overlayableInfo.actor != null) {
String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
overlayableInfo.actor, mOverlayableCallback.getNamedActors()).first;
if (mOverlayableCallback.signaturesMatching(actorPackageName,

View File

@@ -50,8 +50,8 @@ public class OverlayActorEnforcer {
/**
* @return nullable actor result with {@link ActorState} failure status
*/
static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
Map<String, Map<String, String>> namedActors) {
static Pair<String, ActorState> getPackageNameForActor(@NonNull String actorUriString,
@NonNull Map<String, Map<String, String>> namedActors) {
Uri actorUri = Uri.parse(actorUriString);
String actorScheme = actorUri.getScheme();

View File

@@ -45,6 +45,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.ApkAssets;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
@@ -62,6 +63,7 @@ import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.content.om.OverlayConfig;
import com.android.server.FgThread;
import com.android.server.IoThread;
@@ -246,7 +248,7 @@ public final class OverlayManagerService extends SystemService {
new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
mPackageManager = new PackageManagerHelperImpl(context);
mUserManager = UserManagerService.getInstance();
IdmapManager im = new IdmapManager(mPackageManager);
IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager);
mSettings = new OverlayManagerSettings();
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(),
@@ -1118,6 +1120,17 @@ public final class OverlayManagerService extends SystemService {
return false;
}
@Override
public String getOverlayableConfigurator() {
return Resources.getSystem().getString(R.string.config_overlayableConfigurator);
}
@Override
public String[] getOverlayableConfiguratorTargets() {
return Resources.getSystem().getStringArray(
R.array.config_overlayableConfiguratorTargets);
}
@Override
public List<PackageInfo> getOverlayPackages(final int userId) {
final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);

View File

@@ -80,4 +80,24 @@ public interface OverlayableInfoCallback {
* in the system returns {@link PackageManager#SIGNATURE_MATCH}
*/
boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId);
/**
* Retrieves the package name that is recognized as an actor for the packages specified by
* {@link #getOverlayableConfiguratorTargets()}.
*/
@NonNull
default String getOverlayableConfigurator() {
return "";
}
/**
* Retrieves the target packages that recognize the {@link #getOverlayableConfigurator} as an
* actor for its overlayable declarations. Overlays targeting one of the specified targets that
* are signed with the same signature as the overlayable configurator will be granted the
* "actor" policy.
*/
@NonNull
default String[] getOverlayableConfiguratorTargets() {
return new String[0];
}
}

View File

@@ -44,9 +44,9 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testUpdateOverlaysForUser() {
final OverlayManagerServiceImpl impl = getImpl();
installTargetPackage(TARGET, USER);
installTargetPackage("some.other.target", USER);
installOverlayPackage(OVERLAY, TARGET, USER);
addSystemPackage(target(TARGET), USER);
addSystemPackage(target("some.other.target"), USER);;
addSystemPackage(overlay(OVERLAY, TARGET), USER);
// do nothing, expect no change
final List<String> a = impl.updateOverlaysForUser(USER);
@@ -54,8 +54,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
assertTrue(a.contains(TARGET));
// upgrade overlay, keep target
beginUpgradeOverlayPackage(OVERLAY, USER);
endUpgradeOverlayPackage(OVERLAY, TARGET, USER);
addSystemPackage(overlay(OVERLAY, TARGET), USER);
final List<String> b = impl.updateOverlaysForUser(USER);
assertEquals(1, b.size());
@@ -67,7 +66,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
assertTrue(c.contains(TARGET));
// upgrade overlay, switch to new target
addOverlayPackage(OVERLAY, "some.other.target", USER, true, false, 0);
addSystemPackage(overlay(OVERLAY, "some.other.target"), USER);
final List<String> d = impl.updateOverlaysForUser(USER);
assertEquals(2, d.size());
assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
@@ -81,23 +80,24 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testImmutableEnabledChange() {
final OverlayManagerServiceImpl impl = getImpl();
installTargetPackage(TARGET, USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertFalse(o1.isMutable);
addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o2);
assertTrue(o2.isEnabled());
assertFalse(o2.isMutable);
addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
@@ -108,23 +108,24 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testMutableEnabledChangeHasNoEffect() {
final OverlayManagerServiceImpl impl = getImpl();
installTargetPackage(TARGET, USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertTrue(o1.isMutable);
addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o2);
assertFalse(o2.isEnabled());
assertTrue(o2.isMutable);
addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
@@ -135,15 +136,16 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testMutableEnabledToImmutableEnabled() {
final OverlayManagerServiceImpl impl = getImpl();
installTargetPackage(TARGET, USER);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
addOverlayPackage(OVERLAY, TARGET, USER, mutable, enabled, 0);
configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(enabled, o1.isEnabled());
assertEquals(mutable, o1.isMutable);
final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o);
assertEquals(enabled, o.isEnabled());
assertEquals(mutable, o.isMutable);
};
// Immutable/enabled -> mutable/enabled
@@ -178,70 +180,76 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI
@Test
public void testMutablePriorityChange() {
final OverlayManagerServiceImpl impl = getImpl();
installTargetPackage(TARGET, USER);
addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 1);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
assertFalse(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
assertFalse(o2.isEnabled());
// Overlay priority changing between reboots should not affect enable state of mutable
// overlays
// overlays.
impl.setEnabled(OVERLAY, true, USER);
// Reorder the overlays
addOverlayPackage(OVERLAY, TARGET, USER, true, true, 1);
addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 0);
configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
assertTrue(o1.isEnabled());
assertFalse(o4.isEnabled());
}
@Test
public void testImmutablePriorityChange() {
final OverlayManagerServiceImpl impl = getImpl();
installTargetPackage(TARGET, USER);
addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 1);
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
assertTrue(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
// Overlay priority changing between reboots should not affect enable state of mutable
// overlays
impl.setEnabled(OVERLAY, true, USER);
assertTrue(o2.isEnabled());
// Reorder the overlays
addOverlayPackage(OVERLAY, TARGET, USER, false, true, 1);
addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 0);
configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 1 /* priority */);
configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
assertTrue(o1.isEnabled());
assertTrue(o4.isEnabled());
}
}

View File

@@ -26,6 +26,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.om.OverlayInfo;
import android.os.OverlayablePolicy;
import androidx.test.runner.AndroidJUnit4;
@@ -49,11 +50,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
private static final String OVERLAY3 = OVERLAY + "3";
private static final int USER3 = USER2 + 1;
// tests: basics
@Test
public void testGetOverlayInfo() throws Exception {
installOverlayPackage(OVERLAY, TARGET, USER);
public void testGetOverlayInfo() {
installNewPackage(overlay(OVERLAY, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
@@ -64,10 +63,10 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
}
@Test
public void testGetOverlayInfosForTarget() throws Exception {
installOverlayPackage(OVERLAY, TARGET, USER);
installOverlayPackage(OVERLAY2, TARGET, USER);
installOverlayPackage(OVERLAY3, TARGET, USER2);
public void testGetOverlayInfosForTarget() {
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installNewPackage(overlay(OVERLAY3, TARGET), USER2);
final OverlayManagerServiceImpl impl = getImpl();
final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
@@ -89,11 +88,11 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
}
@Test
public void testGetOverlayInfosForUser() throws Exception {
installTargetPackage(TARGET, USER);
installOverlayPackage(OVERLAY, TARGET, USER);
installOverlayPackage(OVERLAY2, TARGET, USER);
installOverlayPackage(OVERLAY3, TARGET2, USER);
public void testGetOverlayInfosForUser() {
installNewPackage(target(TARGET), USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installNewPackage(overlay(OVERLAY3, TARGET2), USER);
final OverlayManagerServiceImpl impl = getImpl();
final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -116,91 +115,86 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
}
@Test
public void testPriority() throws Exception {
installOverlayPackage(OVERLAY, TARGET, USER);
installOverlayPackage(OVERLAY2, TARGET, USER);
installOverlayPackage(OVERLAY3, TARGET, USER);
public void testPriority() {
installNewPackage(overlay(OVERLAY, TARGET), USER);
installNewPackage(overlay(OVERLAY2, TARGET), USER);
installNewPackage(overlay(OVERLAY3, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
assertOverlayInfoList(TARGET, USER, o1, o2, o3);
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertTrue(impl.setLowestPriority(OVERLAY3, USER));
assertOverlayInfoList(TARGET, USER, o3, o1, o2);
assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
assertTrue(impl.setHighestPriority(OVERLAY3, USER));
assertOverlayInfoList(TARGET, USER, o1, o2, o3);
assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER));
assertOverlayInfoList(TARGET, USER, o2, o1, o3);
assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
}
@Test
public void testOverlayInfoStateTransitions() throws Exception {
public void testOverlayInfoStateTransitions() {
final OverlayManagerServiceImpl impl = getImpl();
assertNull(impl.getOverlayInfo(OVERLAY, USER));
installOverlayPackage(OVERLAY, TARGET, USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
installTargetPackage(TARGET, USER);
final DummyDeviceState.PackageBuilder target = target(TARGET);
installNewPackage(target, USER);
assertState(STATE_DISABLED, OVERLAY, USER);
impl.setEnabled(OVERLAY, true, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
// target upgrades do not change the state of the overlay
beginUpgradeTargetPackage(TARGET, USER);
upgradePackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
endUpgradeTargetPackage(TARGET, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
uninstallTargetPackage(TARGET, USER);
uninstallPackage(TARGET, USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
installTargetPackage(TARGET, USER);
installNewPackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
}
@Test
public void testOnOverlayPackageUpgraded() throws Exception {
final OverlayManagerServiceImpl impl = getImpl();
public void testOnOverlayPackageUpgraded() {
final DummyListener listener = getListener();
installTargetPackage(TARGET, USER);
installOverlayPackage(OVERLAY, TARGET, USER);
impl.onOverlayPackageReplacing(OVERLAY, USER);
final DummyDeviceState.PackageBuilder target = target(TARGET);
final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
installNewPackage(target, USER);
installNewPackage(overlay, USER);
listener.count = 0;
impl.onOverlayPackageReplaced(OVERLAY, USER);
assertEquals(1, listener.count);
// upgrade to a version where the overlay has changed its target
beginUpgradeOverlayPackage(OVERLAY, USER);
listener.count = 0;
endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
// expect once for the old target package, once for the new target package
upgradePackage(overlay, USER);
assertEquals(2, listener.count);
beginUpgradeOverlayPackage(OVERLAY, USER);
// upgrade to a version where the overlay has changed its target
// expect once for the old target package, once for the new target package
listener.count = 0;
endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
assertEquals(1, listener.count);
final DummyDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
upgradePackage(overlay2, USER);
assertEquals(3, listener.count);
listener.count = 0;
upgradePackage(overlay2, USER);
assertEquals(2, listener.count);
}
// tests: listener interface
@Test
public void testListener() throws Exception {
public void testListener() {
final OverlayManagerServiceImpl impl = getImpl();
final DummyListener listener = getListener();
installOverlayPackage(OVERLAY, TARGET, USER);
installNewPackage(overlay(OVERLAY, TARGET), USER);
assertEquals(1, listener.count);
listener.count = 0;
installTargetPackage(TARGET, USER);
installNewPackage(target(TARGET), USER);
assertEquals(1, listener.count);
listener.count = 0;
@@ -211,4 +205,49 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes
impl.setEnabled(OVERLAY, true, USER);
assertEquals(0, listener.count);
}
@Test
public void testConfigurator() {
final DummyPackageManagerHelper packageManager = getPackageManager();
packageManager.overlayableConfigurator = "actor";
packageManager.overlayableConfiguratorTargets = new String[]{TARGET};
reinitializeImpl();
installNewPackage(target("actor").setCertificate("one"), USER);
installNewPackage(target(TARGET)
.addOverlayable("TestResources")
.setCertificate("two"), USER);
final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
.setCertificate("one");
installNewPackage(overlay, USER);
final DummyIdmapDaemon idmapDaemon = getIdmapDaemon();
final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath);
assertNotNull(idmap);
assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
}
@Test
public void testConfiguratorDifferentSignatures() {
final DummyPackageManagerHelper packageManager = getPackageManager();
packageManager.overlayableConfigurator = "actor";
packageManager.overlayableConfiguratorTargets = new String[]{TARGET};
reinitializeImpl();
installNewPackage(target("actor").setCertificate("one"), USER);
installNewPackage(target(TARGET)
.addOverlayable("TestResources")
.setCertificate("two"), USER);
final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
.setCertificate("two");
installNewPackage(overlay, USER);
final DummyIdmapDaemon idmapDaemon = getIdmapDaemon();
final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath);
assertNotNull(idmap);
assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.om;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -26,17 +27,19 @@ import android.content.om.OverlayInfo.State;
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.annotation.Nullable;
import com.android.internal.content.om.OverlayConfig;
import org.junit.Assert;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -47,29 +50,48 @@ class OverlayManagerServiceImplTestsBase {
private OverlayManagerServiceImpl mImpl;
private DummyDeviceState mState;
private DummyListener mListener;
private DummyPackageManagerHelper mPackageManager;
private DummyIdmapDaemon mIdmapDaemon;
private OverlayConfig mOverlayConfig;
@Before
public void setUp() {
mState = new DummyDeviceState();
mListener = new DummyListener();
final DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState);
mPackageManager = new DummyPackageManagerHelper(mState);
mIdmapDaemon = new DummyIdmapDaemon(mState);
mOverlayConfig = mock(OverlayConfig.class);
when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY);
when(mOverlayConfig.isEnabled(any())).thenReturn(false);
when(mOverlayConfig.isMutable(any())).thenReturn(true);
reinitializeImpl();
}
mImpl = new OverlayManagerServiceImpl(pmh,
new DummyIdmapManager(mState, pmh),
void reinitializeImpl() {
mImpl = new OverlayManagerServiceImpl(mPackageManager,
new IdmapManager(mIdmapDaemon, mPackageManager),
new OverlayManagerSettings(),
mState.mOverlayConfig,
mOverlayConfig,
new String[0],
mListener);
}
public OverlayManagerServiceImpl getImpl() {
OverlayManagerServiceImpl getImpl() {
return mImpl;
}
public DummyListener getListener() {
DummyListener getListener() {
return mListener;
}
DummyPackageManagerHelper getPackageManager() {
return mPackageManager;
}
DummyIdmapDaemon getIdmapDaemon() {
return mIdmapDaemon;
}
void assertState(@State int expected, final String overlayPackageName, int userId) {
final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
if (info == null) {
@@ -81,7 +103,7 @@ class OverlayManagerServiceImplTestsBase {
assertEquals(msg, expected, info.state);
}
void assertOverlayInfoList(final String targetPackageName, int userId,
void assertOverlayInfoForTarget(final String targetPackageName, int userId,
OverlayInfo... overlayInfos) {
final List<OverlayInfo> expected =
mImpl.getOverlayInfosForTarget(targetPackageName, userId);
@@ -89,198 +111,202 @@ class OverlayManagerServiceImplTestsBase {
assertEquals(expected, actual);
}
/**
* Creates an overlay configured through {@link OverlayConfig}.
*
* @throws IllegalStateException if the package is already installed
*/
void addOverlayPackage(String packageName, String targetPackageName, int userId,
boolean mutable, boolean enabled, int priority) {
mState.addOverlay(packageName, targetPackageName, userId, mutable, enabled, priority);
DummyDeviceState.PackageBuilder target(String packageName) {
return new DummyDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
null /* targetOverlayableName */);
}
DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) {
return overlay(packageName, targetPackageName, null /* targetOverlayableName */);
}
DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName,
String targetOverlayableName) {
return new DummyDeviceState.PackageBuilder(packageName, targetPackageName,
targetOverlayableName);
}
void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
mState.add(pkg, userId);
}
void configureSystemOverlay(String packageName, boolean mutable, boolean enabled,
int priority) {
when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
}
/**
* Adds the target package to the device.
* Adds the package to the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
*
* @throws IllegalStateException if the package is not currently installed
* @throws IllegalStateException if the package is currently installed
*/
void installTargetPackage(String packageName, int userId) {
if (mState.select(packageName, userId) != null) {
throw new IllegalStateException("package already installed");
void installNewPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
if (mState.select(pkg.packageName, userId) != null) {
throw new IllegalStateException("package " + pkg.packageName + " already installed");
}
mState.add(pkg, userId);
if (pkg.targetPackage == null) {
mImpl.onTargetPackageAdded(pkg.packageName, userId);
} else {
mImpl.onOverlayPackageAdded(pkg.packageName, userId);
}
mState.addTarget(packageName, userId);
mImpl.onTargetPackageAdded(packageName, userId);
}
/**
* Begins upgrading the target package.
* Begins upgrading the package.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
* {@link android.content.Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
void beginUpgradeTargetPackage(String packageName, int userId) {
if (mState.select(packageName, userId) == null) {
throw new IllegalStateException("package not installed");
}
mImpl.onTargetPackageReplacing(packageName, userId);
}
/**
* Ends upgrading the target package.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
* {@link android.content.Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
void endUpgradeTargetPackage(String packageName, int userId) {
if (mState.select(packageName, userId) == null) {
throw new IllegalStateException("package not installed");
void upgradePackage(DummyDeviceState.PackageBuilder pkg, int userId) {
final DummyDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
if (replacedPackage == null) {
throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
if (replacedPackage.targetPackageName != null) {
mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
}
mState.add(pkg, userId);
if (pkg.targetPackage == null) {
mImpl.onTargetPackageReplaced(pkg.packageName, userId);
} else {
mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
}
mState.addTarget(packageName, userId);
mImpl.onTargetPackageReplaced(packageName, userId);
}
/**
* Removes the target package from the device.
* Removes the package from the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
*
* @throws IllegalStateException if the package is not currently installed
*/
void uninstallTargetPackage(String packageName, int userId) {
if (mState.select(packageName, userId) == null) {
throw new IllegalStateException("package not installed");
void uninstallPackage(String packageName, int userId) {
final DummyDeviceState.Package pkg = mState.select(packageName, userId);
if (pkg == null) {
throw new IllegalStateException("package " + packageName+ " not installed");
}
mState.remove(pkg.packageName);
if (pkg.targetPackageName == null) {
mImpl.onTargetPackageRemoved(pkg.packageName, userId);
} else {
mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
}
mState.remove(packageName, userId);
mImpl.onTargetPackageRemoved(packageName, userId);
}
/**
* Adds the overlay package to the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
*
* @throws IllegalStateException if the package is already installed
*/
void installOverlayPackage(String packageName, String targetPackageName, int userId) {
if (mState.select(packageName, userId) != null) {
throw new IllegalStateException("package already installed");
}
mState.addOverlay(packageName, targetPackageName, userId);
mImpl.onOverlayPackageAdded(packageName, userId);
}
/** Represents the state of packages installed on a fake device. */
static class DummyDeviceState {
private ArrayMap<String, Package> mPackages = new ArrayMap<>();
/**
* Begins upgrading the overlay package.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
* {@link android.content.Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
void beginUpgradeOverlayPackage(String packageName, int userId) {
if (mState.select(packageName, userId) == null) {
throw new IllegalStateException("package not installed, cannot upgrade");
}
void add(PackageBuilder pkgBuilder, int userId) {
final Package pkg = pkgBuilder.build();
final Package previousPkg = select(pkg.packageName, userId);
mPackages.put(pkg.packageName, pkg);
mImpl.onOverlayPackageReplacing(packageName, userId);
}
/**
* Ends upgrading the overlay package, potentially changing its target package.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
* {@link android.content.Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
void endUpgradeOverlayPackage(String packageName, String targetPackageName, int userId) {
if (mState.select(packageName, userId) == null) {
throw new IllegalStateException("package not installed, cannot upgrade");
}
mState.addOverlay(packageName, targetPackageName, userId);
mImpl.onOverlayPackageReplaced(packageName, userId);
}
private static final class DummyDeviceState {
private List<Package> mPackages = new ArrayList<>();
private OverlayConfig mOverlayConfig = mock(OverlayConfig.class);
/** Adds a non-overlay to the device. */
public void addTarget(String packageName, int userId) {
remove(packageName, userId);
mPackages.add(new Package(packageName, userId, null, false, false, 0));
}
/** Adds an overlay to the device. */
public void addOverlay(String packageName, String targetPackageName, int userId) {
addOverlay(packageName, targetPackageName, userId, true, false, OverlayConfig.DEFAULT_PRIORITY);
}
/** Adds a configured overlay to the device. */
public void addOverlay(String packageName, String targetPackageName, int userId,
boolean mutable, boolean enabled, int priority) {
remove(packageName, userId);
mPackages.add(new Package(packageName, userId, targetPackageName, mutable, enabled,
priority));
when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
}
/** Remove a package from the device. */
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;
}
pkg.installedUserIds.add(userId);
if (previousPkg != null) {
pkg.installedUserIds.addAll(previousPkg.installedUserIds);
}
}
/** Retrieves all packages on device for a particular user. */
public List<Package> select(int userId) {
return mPackages.stream().filter(p -> p.userId == userId).collect(Collectors.toList());
void remove(String packageName) {
mPackages.remove(packageName);
}
/** Retrieves the package with the specified package name for a particular user. */
public Package select(String packageName, int userId) {
return mPackages.stream().filter(
p -> p.packageName.equals(packageName) && p.userId == userId)
.findFirst().orElse(null);
void uninstall(String packageName, int userId) {
final Package pkg = mPackages.get(packageName);
if (pkg != null) {
pkg.installedUserIds.remove(userId);
}
}
private static final class Package {
public final String packageName;
public final int userId;
public final String targetPackageName;
public final boolean mutable;
public final boolean enabled;
public final int priority;
List<Package> select(int userId) {
return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
.collect(Collectors.toList());
}
private Package(String packageName, int userId, String targetPackageName,
boolean mutable, boolean enabled, int priority) {
Package select(String packageName, int userId) {
final Package pkg = mPackages.get(packageName);
return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
}
private Package selectFromPath(String path) {
return mPackages.values().stream()
.filter(p -> p.apkPath.equals(path)).findFirst().orElse(null);
}
static final class PackageBuilder {
private String packageName;
private String targetPackage;
private String certificate = "[default]";
private int version = 0;
private ArrayList<String> overlayableNames = new ArrayList<>();
private String targetOverlayableName;
private PackageBuilder(String packageName, String targetPackage,
String targetOverlayableName) {
this.packageName = packageName;
this.targetPackage = targetPackage;
this.targetOverlayableName = targetOverlayableName;
}
PackageBuilder setCertificate(String certificate) {
this.certificate = certificate;
return this;
}
PackageBuilder addOverlayable(String overlayableName) {
overlayableNames.add(overlayableName);
return this;
}
PackageBuilder setVersion(int version) {
this.version = version;
return this;
}
Package build() {
final String apkPath = String.format("%s/%s/base.apk",
targetPackage == null ? "/system/app/:" : "/vendor/overlay/:",
packageName);
final Package newPackage = new Package(packageName, targetPackage,
targetOverlayableName, version, apkPath, certificate);
newPackage.overlayableNames.addAll(overlayableNames);
return newPackage;
}
}
static final class Package {
final String packageName;
final String targetPackageName;
final String targetOverlayableName;
final int versionCode;
final String apkPath;
final String certificate;
final ArrayList<String> overlayableNames = new ArrayList<>();
private final ArraySet<Integer> installedUserIds = new ArraySet<>();
private Package(String packageName, String targetPackageName,
String targetOverlayableName, int versionCode, String apkPath,
String certificate) {
this.packageName = packageName;
this.userId = userId;
this.targetPackageName = targetPackageName;
this.mutable = mutable;
this.enabled = enabled;
this.priority = priority;
this.targetOverlayableName = targetOverlayableName;
this.versionCode = versionCode;
this.apkPath = apkPath;
this.certificate = certificate;
}
}
}
@@ -288,6 +314,8 @@ class OverlayManagerServiceImplTestsBase {
static final class DummyPackageManagerHelper implements PackageManagerHelper,
OverlayableInfoCallback {
private final DummyDeviceState mState;
String[] overlayableConfiguratorTargets = new String[0];
String overlayableConfigurator = "";
private DummyPackageManagerHelper(DummyDeviceState state) {
mState = state;
@@ -300,13 +328,12 @@ class OverlayManagerServiceImplTestsBase {
return null;
}
final ApplicationInfo ai = new ApplicationInfo();
ai.sourceDir = String.format("%s/%s/base.apk",
pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/",
pkg.packageName);
ai.sourceDir = pkg.apkPath;
PackageInfo pi = new PackageInfo();
pi.applicationInfo = ai;
pi.packageName = pkg.packageName;
pi.overlayTarget = pkg.targetPackageName;
pi.targetOverlayableName = pkg.targetOverlayableName;
pi.overlayCategory = "dummy-category-" + pkg.targetPackageName;
return pi;
}
@@ -314,14 +341,16 @@ class OverlayManagerServiceImplTestsBase {
@Override
public boolean signaturesMatching(@NonNull String packageName1,
@NonNull String packageName2, int userId) {
return false;
final DummyDeviceState.Package pkg1 = mState.select(packageName1, userId);
final DummyDeviceState.Package pkg2 = mState.select(packageName2, userId);
return pkg1 != null && pkg2 != null && pkg1.certificate.equals(pkg2.certificate);
}
@Override
public List<PackageInfo> getOverlayPackages(int userId) {
return mState.select(userId).stream()
.filter(p -> p.targetPackageName != null)
.map(p -> getPackageInfo(p.packageName, p.userId))
.map(p -> getPackageInfo(p.packageName, userId))
.collect(Collectors.toList());
}
@@ -329,7 +358,11 @@ class OverlayManagerServiceImplTestsBase {
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId) {
throw new UnsupportedOperationException();
final DummyDeviceState.Package pkg = mState.select(packageName, userId);
if (pkg == null || !pkg.overlayableNames.contains(targetOverlayableName)) {
return null;
}
return new OverlayableInfo(targetOverlayableName, null /* actor */);
}
@Nullable
@@ -341,69 +374,98 @@ class OverlayManagerServiceImplTestsBase {
@NonNull
@Override
public Map<String, Map<String, String>> getNamedActors() {
throw new UnsupportedOperationException();
return Collections.emptyMap();
}
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) {
throw new UnsupportedOperationException();
final DummyDeviceState.Package pkg = mState.select(targetPackageName, userId);
return pkg != null && pkg.overlayableNames.contains(targetPackageName);
}
@Override
public void enforcePermission(String permission, String message) throws SecurityException {
throw new UnsupportedOperationException();
}
@Override
public String[] getOverlayableConfiguratorTargets() {
return overlayableConfiguratorTargets;
}
@Override
public String getOverlayableConfigurator() {
return overlayableConfigurator;
}
}
static class DummyIdmapManager extends IdmapManager {
static class DummyIdmapDaemon extends IdmapDaemon {
private final DummyDeviceState mState;
private Set<String> mIdmapFiles = new ArraySet<>();
private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
private DummyIdmapManager(DummyDeviceState state,
DummyPackageManagerHelper packageManagerHelper) {
super(packageManagerHelper);
mState = state;
DummyIdmapDaemon(DummyDeviceState state) {
this.mState = state;
}
private int getCrc(@NonNull final String path) {
final DummyDeviceState.Package pkg = mState.selectFromPath(path);
Assert.assertNotNull(pkg);
return pkg.versionCode;
}
@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) {
String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
int userId) {
mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
getCrc(overlayPath), targetPath, policies, enforce));
return overlayPath;
}
@Override
boolean removeIdmap(String overlayPath, int userId) {
return mIdmapFiles.remove(overlayPath) != null;
}
@Override
boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
int userId) {
final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
if (idmap == null) {
return false;
}
final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId);
if (o == null) {
return false;
return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath);
}
@Override
boolean idmapExists(String overlayPath, int userId) {
return mIdmapFiles.containsKey(overlayPath);
}
IdmapHeader getIdmap(String overlayPath) {
return mIdmapFiles.get(overlayPath);
}
static class IdmapHeader {
private final int targetCrc;
private final int overlayCrc;
final String targetPath;
final int policies;
final boolean enforceOverlayable;
private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
boolean enforceOverlayable) {
this.targetCrc = targetCrc;
this.overlayCrc = overlayCrc;
this.targetPath = targetPath;
this.policies = policies;
this.enforceOverlayable = enforceOverlayable;
}
final String key = createKey(overlayPackage.packageName, userId);
return mIdmapFiles.add(key);
}
@Override
boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
final String key = createKey(oi.packageName, oi.userId);
if (!mIdmapFiles.contains(key)) {
return false;
private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc,
String expectedTargetPath) {
return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc
&& expectedTargetPath.equals(targetPath);
}
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);
}
}