From 3f2b728bba995f99b3518713eb4ba3c53644648b Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Sun, 29 Mar 2020 17:14:59 -0700 Subject: [PATCH] Allow PackageDexUsage to record code paths not used by others The restriction was just an optimization to make the processing simpler and clearer. All packages are expected to use their code paths and recording this info was redundant and added noise. However, in prepartion to record system server code paths we have to record everything that belongs to a package, even if it's not used by others. This CL makes PackageDexUsage agnostic of the isUsedByOther flags: it will record everything that is given to it. Note that this applies only to PackageDexUsage. DexManager may still decide to skip the recording (which it does for non system server apps) Test: atest PackageDexUsageTests DexManagerTests Bug: 148774920 Change-Id: Icdb9ff45330a8fb15d80cb07495723b473228dda --- .../com/android/server/pm/dex/DexManager.java | 35 ++-- .../server/pm/dex/PackageDexUsage.java | 92 +++++---- .../server/pm/dex/PackageDexUsageTests.java | 182 ++++++++++++++---- 3 files changed, 211 insertions(+), 98 deletions(-) diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 5ba1996ed3c9d..9f2ec47335f3e 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -103,19 +103,6 @@ public class DexManager { private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex - /** - * We do not record packages that have no secondary dex files or that are not used by other - * apps. This is an optimization to reduce the amount of data that needs to be written to - * disk (apps will not usually be shared so this trims quite a bit the number we record). - * - * To make this behaviour transparent to the callers which need use information on packages, - * DexManager will return this DEFAULT instance from - * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and - * is marked as not being used by other apps. This reflects the intended behaviour when we don't - * find the package in the underlying data file. - */ - private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo(); - public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock) { mContext = context; @@ -194,6 +181,8 @@ public class DexManager { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. + // Note this is just an optimization that makes things easier to read in the + // package-dex-use file since we don't need to pollute it with redundant info. continue; } @@ -211,7 +200,7 @@ public class DexManager { // async write to disk to make sure we don't loose the data in case of a reboot. if (mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, + dexPath, loaderUserId, loaderIsa, primaryOrSplit, loadingAppInfo.packageName, classLoaderContext)) { mPackageDexUsage.maybeWriteAsync(); } @@ -391,8 +380,17 @@ public class DexManager { * to access the package use. */ public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { + // We do not record packages that have no secondary dex files or that are not used by other + // apps. This is an optimization to reduce the amount of data that needs to be written to + // disk (apps will not usually be shared so this trims quite a bit the number we record). + // + // To make this behaviour transparent to the callers which need use information on packages, + // DexManager will return this DEFAULT instance from + // {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files + // and is marked as not being used by other apps. This reflects the intended behaviour when + // we don't find the package in the underlying data file. PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); - return useInfo == null ? DEFAULT_USE_INFO : useInfo; + return useInfo == null ? new PackageUseInfo(packageName) : useInfo; } /** @@ -542,7 +540,7 @@ public class DexManager { // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the // compilation happening here will use a pessimistic context. public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, - boolean isUsedByOtherApps, int userId) { + boolean isSharedModule, int userId) { // Find the owning package record. DexSearchResult searchResult = getDexPackage(info, dexPath, userId); @@ -559,9 +557,12 @@ public class DexManager { // We found the package. Now record the usage for all declared ISAs. boolean update = false; + // If this is a shared module set the loading package to an arbitrary package name + // so that we can mark that module as usedByOthers. + String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName; for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) { boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, + dexPath, userId, isa, /*primaryOrSplit*/ false, searchResult.mOwningPackageName, PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); update |= newUpdate; diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 8819a0fe439b5..1a3c18f61333d 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -39,10 +39,12 @@ import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -118,7 +120,7 @@ public class PackageDexUsage extends AbstractStatsBase { * has been seen before. */ /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, + String loaderIsa, boolean primaryOrSplit, String loadingPackageName, String classLoaderContext) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); @@ -127,20 +129,22 @@ public class PackageDexUsage extends AbstractStatsBase { throw new IllegalArgumentException("Null classLoaderContext"); } if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) { + Slog.e(TAG, "Unsupported context?"); return false; } + boolean isUsedByOtherApps = !owningPackageName.equals(loadingPackageName); + synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); if (packageUseInfo == null) { // This is the first time we see the package. - packageUseInfo = new PackageUseInfo(); + packageUseInfo = new PackageUseInfo(owningPackageName); if (primaryOrSplit) { // If we have a primary or a split apk, set isUsedByOtherApps. // We do not need to record the loaderIsa or the owner because we compile // primaries for all users and all ISAs. - packageUseInfo.mergeCodePathUsedByOtherApps(dexPath, isUsedByOtherApps, - owningPackageName, loadingPackageName); + packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName); } else { // For secondary dex files record the loaderISA and the owner. We'll need // to know under which user to compile and for what ISA. @@ -156,9 +160,8 @@ public class PackageDexUsage extends AbstractStatsBase { // We already have data on this package. Amend it. if (primaryOrSplit) { // We have a possible update on the primary apk usage. Merge - // isUsedByOtherApps information and return if there was an update. - return packageUseInfo.mergeCodePathUsedByOtherApps( - dexPath, isUsedByOtherApps, owningPackageName, loadingPackageName); + // dex path information and return if there was an update. + return packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName); } else { DexUseInfo newData = new DexUseInfo( isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); @@ -273,7 +276,7 @@ public class PackageDexUsage extends AbstractStatsBase { // Write the code paths used by other apps. for (Map.Entry> codeEntry : - packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { + packageUseInfo.mPrimaryCodePaths.entrySet()) { String codePath = codeEntry.getKey(); Set loadingPackages = codeEntry.getValue(); fpw.println(CODE_PATH_LINE_CHAR + codePath); @@ -408,11 +411,11 @@ public class PackageDexUsage extends AbstractStatsBase { // @loading_packages String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); Set loadingPackages = readLoadingPackages(in, version); - currentPackageData.mCodePathsUsedByOtherApps.put(codePath, loadingPackages); + currentPackageData.mPrimaryCodePaths.put(codePath, loadingPackages); } else { // This is a package line. currentPackage = line; - currentPackageData = new PackageUseInfo(); + currentPackageData = new PackageUseInfo(currentPackage); data.put(currentPackage, currentPackageData); } } @@ -499,7 +502,7 @@ public class PackageDexUsage extends AbstractStatsBase { // Sync the code paths. Set codePaths = packageToCodePaths.get(packageName); Iterator>> codeIt = - packageUseInfo.mCodePathsUsedByOtherApps.entrySet().iterator(); + packageUseInfo.mPrimaryCodePaths.entrySet().iterator(); while (codeIt.hasNext()) { if (!codePaths.contains(codeIt.next().getKey())) { codeIt.remove(); @@ -667,22 +670,26 @@ public class PackageDexUsage extends AbstractStatsBase { * Stores data on how a package and its dex files are used. */ public static class PackageUseInfo { + // The name of the package this info belongs to. + private final String mPackageName; // The app's code paths that are used by other apps. // The key is the code path and the value is the set of loading packages. - private final Map> mCodePathsUsedByOtherApps; + private final Map> mPrimaryCodePaths; // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). private final Map mDexUseInfoMap; - /*package*/ PackageUseInfo() { - mCodePathsUsedByOtherApps = new HashMap<>(); + /*package*/ PackageUseInfo(String packageName) { + mPrimaryCodePaths = new HashMap<>(); mDexUseInfoMap = new HashMap<>(); + mPackageName = packageName; } // Creates a deep copy of the `other`. private PackageUseInfo(PackageUseInfo other) { - mCodePathsUsedByOtherApps = new HashMap<>(); - for (Map.Entry> e : other.mCodePathsUsedByOtherApps.entrySet()) { - mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); + mPackageName = other.mPackageName; + mPrimaryCodePaths = new HashMap<>(); + for (Map.Entry> e : other.mPrimaryCodePaths.entrySet()) { + mPrimaryCodePaths.put(e.getKey(), new HashSet<>(e.getValue())); } mDexUseInfoMap = new HashMap<>(); @@ -691,28 +698,29 @@ public class PackageDexUsage extends AbstractStatsBase { } } - private boolean mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, - String owningPackageName, String loadingPackage) { - if (!isUsedByOtherApps) { - // Nothing to update if the the code path is not used by other apps. - return false; - } - - boolean newCodePath = false; - Set loadingPackages = mCodePathsUsedByOtherApps.get(codePath); + private boolean mergePrimaryCodePaths(String codePath, String loadingPackage) { + Set loadingPackages = mPrimaryCodePaths.get(codePath); if (loadingPackages == null) { loadingPackages = new HashSet<>(); - mCodePathsUsedByOtherApps.put(codePath, loadingPackages); - newCodePath = true; + mPrimaryCodePaths.put(codePath, loadingPackages); } - boolean newLoadingPackage = loadingPackage != null - && !loadingPackage.equals(owningPackageName) - && loadingPackages.add(loadingPackage); - return newCodePath || newLoadingPackage; + return loadingPackages.add(loadingPackage); } public boolean isUsedByOtherApps(String codePath) { - return mCodePathsUsedByOtherApps.containsKey(codePath); + if (mPrimaryCodePaths.containsKey(codePath)) { + Set loadingPackages = mPrimaryCodePaths.get(codePath); + if (loadingPackages.contains(mPackageName)) { + // If the owning package is in the list then this code path + // is used by others if there are other packages in the list. + return loadingPackages.size() > 1; + } else { + // The owning package is not in the loading packages. So if + // the list is non-empty then the code path is used by others. + return !loadingPackages.isEmpty(); + } + } + return false; } public Map getDexUseInfoMap() { @@ -720,11 +728,11 @@ public class PackageDexUsage extends AbstractStatsBase { } public Set getLoadingPackages(String codePath) { - return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); + return mPrimaryCodePaths.getOrDefault(codePath, null); } public boolean isAnyCodePathUsedByOtherApps() { - return !mCodePathsUsedByOtherApps.isEmpty(); + return !mPrimaryCodePaths.isEmpty(); } /** @@ -732,12 +740,16 @@ public class PackageDexUsage extends AbstractStatsBase { * Returns whether or not there was an update. */ /*package*/ boolean clearCodePathUsedByOtherApps() { - if (mCodePathsUsedByOtherApps.isEmpty()) { - return false; - } else { - mCodePathsUsedByOtherApps.clear(); - return true; + boolean updated = false; + List retainOnlyOwningPackage = new ArrayList<>(1); + retainOnlyOwningPackage.add(mPackageName); + for (Map.Entry> entry : mPrimaryCodePaths.entrySet()) { + // Remove or loading packages but the owning one. + if (entry.getValue().retainAll(retainOnlyOwningPackage)) { + updated = true; + } } + return updated; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index f01ddfca15c62..08fc87758f57d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -77,25 +77,25 @@ public class PackageDexUsageTests { String fooDataDir = "/data/user/0/com.google.foo/"; mFooBaseUser0 = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, ISA, false, true, fooPackageName); + fooCodeDir + "base.apk", 0, ISA, true, fooPackageName); mFooSplit1User0 = new TestData(fooPackageName, - fooCodeDir + "split-1.apk", 0, ISA, false, true, fooPackageName); + fooCodeDir + "split-1.apk", 0, ISA, true, fooPackageName); mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, - fooCodeDir + "split-2.apk", 0, ISA, true, true, "used.by.other.com"); + fooCodeDir + "split-2.apk", 0, ISA, true, "used.by.other.com"); mFooSecondary1User0 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 0, ISA, false, false, fooPackageName); + fooDataDir + "sec-1.dex", 0, ISA, false, fooPackageName); mFooSecondary1User1 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 1, ISA, false, false, fooPackageName); + fooDataDir + "sec-1.dex", 1, ISA, false, fooPackageName); mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, - fooDataDir + "sec-2.dex", 0, ISA, true, false, "used.by.other.com"); + fooDataDir + "sec-2.dex", 0, ISA, false, "used.by.other.com"); mInvalidIsa = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER"); + fooCodeDir + "base.apk", 0, "INVALID_ISA", true, "INALID_USER"); String barPackageName = "com.google.bar"; String barCodeDir = "/data/app/com.google.bar/"; @@ -103,11 +103,11 @@ public class PackageDexUsageTests { String barDataDir1 = "/data/user/1/com.google.bar/"; mBarBaseUser0 = new TestData(barPackageName, - barCodeDir + "base.apk", 0, ISA, false, true, barPackageName); + barCodeDir + "base.apk", 0, ISA, true, barPackageName); mBarSecondary1User0 = new TestData(barPackageName, - barDataDir + "sec-1.dex", 0, ISA, false, false, barPackageName); + barDataDir + "sec-1.dex", 0, ISA, false, barPackageName); mBarSecondary2User1 = new TestData(barPackageName, - barDataDir1 + "sec-2.dex", 1, ISA, false, false, barPackageName); + barDataDir1 + "sec-2.dex", 1, ISA, false, barPackageName); } @Test @@ -134,7 +134,9 @@ public class PackageDexUsageTests { public void testRecordSplitPrimarySequence() { // Assert new information. assertTrue(record(mFooBaseUser0)); - // Assert no new information. + assertTrue(record(mFooSplit1User0)); + // Assert no new information if we add again + assertFalse(record(mFooBaseUser0)); assertFalse(record(mFooSplit1User0)); assertPackageDexUsage(mFooBaseUser0); @@ -192,7 +194,7 @@ public class PackageDexUsageTests { for (int i = 1; i <= tooManyFiles; i++) { String fooPackageName = "com.google.foo"; TestData testData = new TestData(fooPackageName, - "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, false, + "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, fooPackageName); if (i < tooManyFiles) { assertTrue("Adding " + testData.mDexFile, record(testData)); @@ -200,7 +202,11 @@ public class PackageDexUsageTests { } else { assertFalse("Adding " + testData.mDexFile, record(testData)); } - assertPackageDexUsage(mPackageDexUsage, null, null, expectedSecondaries); + assertPackageDexUsage( + mPackageDexUsage, + /* usdeBy=*/ (Set) null, + /* primaryDex= */ null, + expectedSecondaries); } } @@ -345,9 +351,8 @@ public class PackageDexUsageTests { mFooSplit2UsedByOtherApps0.mDexFile, mFooSplit2UsedByOtherApps0.mOwnerUserId, mFooSplit2UsedByOtherApps0.mLoaderIsa, - /*mIsUsedByOtherApps*/false, mFooSplit2UsedByOtherApps0.mPrimaryOrSplit, - mFooSplit2UsedByOtherApps0.mUsedBy); + /*usedBy=*/ null); assertPackageDexUsage(noLongerUsedByOtherApps); } @@ -371,19 +376,19 @@ public class PackageDexUsageTests { assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, usersExtra)); packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); // Verify that the users were recorded. Set userAll = new HashSet<>(users); userAll.addAll(usersExtra); assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0, - mFooSecondary1User0); + mFooSecondary2UsedByOtherApps0); } @Test - public void testRecordDexFileUsersNotTheOwningPackage() { + public void testRecordDexFileUsersAndTheOwningPackage() { PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); Set users = new HashSet<>(Arrays.asList( new String[] {mFooSplit2UsedByOtherApps0.mPackageName})); @@ -393,13 +398,13 @@ public class PackageDexUsageTests { assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); - assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); - packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); - // Verify that only the non owning packages were recorded. - assertPackageDexUsage(packageDexUsageRecordUsers, usersExtra, mFooSplit2UsedByOtherApps0, - mFooSecondary1User0); + + Set expectedUsers = new HashSet<>(users); + expectedUsers.addAll(usersExtra); + // Verify that all loading packages were recorded. + assertPackageDexUsage( + packageDexUsageRecordUsers, expectedUsers, mFooSplit2UsedByOtherApps0); } @Test @@ -435,6 +440,85 @@ public class PackageDexUsageTests { assertTrue(variableContext.isVariableClassLoaderContext()); } + @Test + public void testRead() { + String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); + // Equivalent to + // record(mFooSplit2UsedByOtherApps0); + // record(mFooSecondary1User0); + // record(mFooSecondary2UsedByOtherApps0); + // record(mBarBaseUser0); + // record(mBarSecondary1User0); + String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n" + + "com.google.foo\n" + + "+/data/app/com.google.foo/split-2.apk\n" + + "@used.by.other.com\n" + + "#/data/user/0/com.google.foo/sec-2.dex\n" + + "0,1," + ISA + "\n" + + "@used.by.other.com\n" + + "PCL[/data/user/0/com.google.foo/sec-2.dex]\n" + + "#/data/user/0/com.google.foo/sec-1.dex\n" + + "0,0," + ISA + "\n" + + "@\n" + + "PCL[/data/user/0/com.google.foo/sec-1.dex]\n" + + "com.google.bar\n" + + "+/data/app/com.google.bar/base.apk\n" + + "@com.google.bar\n" + + "#/data/user/0/com.google.bar/sec-1.dex\n" + + "0,0," + ISA + "\n" + + "@\n" + + "PCL[/data/user/0/com.google.bar/sec-1.dex]"; + + PackageDexUsage packageDexUsage = new PackageDexUsage(); + try { + packageDexUsage.read(new StringReader(content)); + } catch (IOException e) { + fail(); + } + + // After the read we must sync the data to fill the missing information on the code paths. + Map> packageToUsersMap = new HashMap<>(); + Map> packageToCodePaths = new HashMap<>(); + + // Handle foo package. + packageToUsersMap.put( + mFooSplit2UsedByOtherApps0.mPackageName, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId))); + packageToCodePaths.put( + mFooSplit2UsedByOtherApps0.mPackageName, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mDexFile, + mFooSplit1User0.mDexFile, mFooBaseUser0.mDexFile))); + // Handle bar package. + packageToUsersMap.put( + mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId))); + packageToCodePaths.put( + mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); + // Handle the loading package. + packageToUsersMap.put( + mFooSplit2UsedByOtherApps0.mUsedBy, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId))); + + // Sync the data. + packageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + + // Assert foo code paths. + assertPackageDexUsage( + packageDexUsage, + /*nonDefaultUsers=*/ null, + mFooSplit2UsedByOtherApps0, + mFooSecondary2UsedByOtherApps0, + mFooSecondary1User0); + + // Assert bar code paths. + assertPackageDexUsage( + packageDexUsage, + /*nonDefaultUsers=*/ null, + mBarBaseUser0, + mBarSecondary1User0); + } + @Test public void testUnsupportedClassLoaderDiscardedOnRead() throws Exception { String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n" @@ -470,16 +554,18 @@ public class PackageDexUsageTests { String packageName = primary == null ? secondaries.get(0).mPackageName : primary.mPackageName; - boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps; + boolean primaryUsedByOtherApps = primary != null && primary.isUsedByOtherApps(); PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); // Check package use info assertNotNull(pInfo); if (primary != null) { - assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile)); if (users != null) { assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users); + } else if (pInfo.getLoadingPackages(primary.mDexFile) != null) { + assertEquals(pInfo.getLoadingPackages(primary.mDexFile), primary.getUsedBy()); } + assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile)); } Map dexUseInfoMap = pInfo.getDexUseInfoMap(); @@ -489,13 +575,15 @@ public class PackageDexUsageTests { for (TestData testData : secondaries) { DexUseInfo dInfo = dexUseInfoMap.get(testData.mDexFile); assertNotNull(dInfo); - assertEquals(testData.mUsedByOtherApps, dInfo.isUsedByOtherApps()); + if (users != null) { + assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), users); + } else { + assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), testData.getUsedBy()); + } + assertEquals(testData.isUsedByOtherApps(), dInfo.isUsedByOtherApps()); assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId()); assertEquals(1, dInfo.getLoaderIsas().size()); assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa)); - if (users != null) { - assertEquals(dInfo.getLoadingPackages(), users); - } assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext()); } @@ -503,7 +591,7 @@ public class PackageDexUsageTests { private boolean record(TestData testData) { return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext); } @@ -511,7 +599,7 @@ public class PackageDexUsageTests { boolean result = true; for (String user : users) { result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mPrimaryOrSplit, user, testData.mClassLoaderContext); } return result; @@ -540,37 +628,49 @@ public class PackageDexUsageTests { private final String mDexFile; private final int mOwnerUserId; private final String mLoaderIsa; - private final boolean mUsedByOtherApps; private final boolean mPrimaryOrSplit; private final String mUsedBy; private final String mClassLoaderContext; private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { - this(packageName, dexFile, ownerUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, - usedBy, "DefaultClassLoaderContextFor_" + dexFile); + String loaderIsa, boolean primaryOrSplit, String usedBy) { + this(packageName, dexFile, ownerUserId, loaderIsa, primaryOrSplit, + usedBy, "PCL[" + dexFile + "]"); } private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy, + String loaderIsa, boolean primaryOrSplit, String usedBy, String classLoaderContext) { mPackageName = packageName; mDexFile = dexFile; mOwnerUserId = ownerUserId; mLoaderIsa = loaderIsa; - mUsedByOtherApps = isUsedByOtherApps; mPrimaryOrSplit = primaryOrSplit; mUsedBy = usedBy; mClassLoaderContext = classLoaderContext; } private TestData updateClassLoaderContext(String newContext) { - return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mUsedByOtherApps, + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mPrimaryOrSplit, mUsedBy, newContext); } private TestData updateUseByOthers(boolean newUsedByOthers) { - return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, newUsedByOthers, + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mPrimaryOrSplit, mUsedBy, mClassLoaderContext); } + + private boolean isUsedByOtherApps() { + return mUsedBy != null && !mPackageName.equals(mUsedBy); + } + + private Set getUsedBy() { + Set users = new HashSet<>(); + if ((mUsedBy != null) && (mPrimaryOrSplit || isUsedByOtherApps())) { + // We do not store the loading package for secondary dex files + // which are not used by others. + users.add(mUsedBy); + } + return users; + } } }