From: Calin Juravle Date: Sat, 4 Feb 2017 00:55:49 +0000 (-0800) Subject: Record dex files users in the dex-usage list X-Git-Tag: android-x86-9.0-r1~339^2^2~6^2~4^2^2~1 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=09844b54266f8ebd3a6d60082204b60e45404c6f;p=android-x86%2Fframeworks-base.git Record dex files users in the dex-usage list Add the users of the dex files in the package-dex-usage.list. This will provide more data on why a package is marked as shared and not optimized using profiles. (cherry picked from commit 535a4753e313bdc2ae3e8be9f50606b82edcce0c) Test: runtest -x .../DexManagerTests.java users of the dex files are recorded in package-dex-usage.list Bug: 63778376 Merged-In: I329bc929b17fa0afe1531f3e6879f6160157a787 Change-Id: I329bc929b17fa0afe1531f3e6879f6160157a787 --- 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 0d4df4d4c9b4..3d2d4833175b 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -148,7 +148,8 @@ public class DexManager { // or UsedBytOtherApps), record will return true and we trigger an 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, isUsedByOtherApps, primaryOrSplit, + loadingAppInfo.packageName)) { mPackageDexUsage.maybeWriteAsync(); } } else { @@ -455,7 +456,8 @@ public class DexManager { for (String isa : getAppDexInstructionSets(info)) { isas.add(isa); boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false); + dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, + searchResult.mOwningPackageName); update |= newUpdate; } if (update) { 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 8a66f12cb6d9..f7dd174847a1 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -35,14 +35,19 @@ import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import dalvik.system.VMRuntime; import libcore.io.IoUtils; +import libcore.util.Objects; /** * Stat file which store usage information about dex files. @@ -50,19 +55,23 @@ import libcore.io.IoUtils; public class PackageDexUsage extends AbstractStatsBase { private final static String TAG = "PackageDexUsage"; - private final static int PACKAGE_DEX_USAGE_VERSION = 1; + // The last version update: add the list of packages that load the dex files. + private final static int PACKAGE_DEX_USAGE_VERSION = 2; + // We support VERSION 1 to ensure that the usage list remains valid cross OTAs. + private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1; + private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; private final static String SPLIT_CHAR = ","; private final static String DEX_LINE_CHAR = "#"; - + private final static String LOADING_PACKAGE_CHAR = "@"; // Map which structures the information we have on a package. // Maps package name to package data (which stores info about UsedByOtherApps and // secondary dex files.). // Access to this map needs synchronized. @GuardedBy("mPackageUseInfoMap") - private Map mPackageUseInfoMap; + private final Map mPackageUseInfoMap; public PackageDexUsage() { super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false); @@ -75,18 +84,21 @@ public class PackageDexUsage extends AbstractStatsBase { * Note this is called when apps load dex files and as such it should return * as fast as possible. * - * @param loadingPackage the package performing the load + * @param owningPackageName the package owning the dex path * @param dexPath the path of the dex files being loaded * @param ownerUserId the user id which runs the code loading the dex files * @param loaderIsa the ISA of the app loading the dex files * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates * the file is either primary or a split. False indicates the file is secondary dex. + * @param loadingPackageName the package performing the load. Recorded only if it is different + * than {@param owningPackageName}. * @return true if the dex load constitutes new information, or false if this information * has been seen before. */ public boolean record(String owningPackageName, String dexPath, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, + String loadingPackageName) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); } @@ -100,11 +112,15 @@ public class PackageDexUsage extends AbstractStatsBase { // We do not need to record the loaderIsa or the owner because we compile // primaries for all users and all ISAs. packageUseInfo.mIsUsedByOtherApps = isUsedByOtherApps; + maybeAddLoadingPackage(owningPackageName, loadingPackageName, + packageUseInfo.mLoadingPackages); } 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. - packageUseInfo.mDexUseInfoMap.put( - dexPath, new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa)); + DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa); + packageUseInfo.mDexUseInfoMap.put(dexPath, newData); + maybeAddLoadingPackage(owningPackageName, loadingPackageName, + newData.mLoadingPackages); } mPackageUseInfoMap.put(owningPackageName, packageUseInfo); return true; @@ -113,10 +129,15 @@ public class PackageDexUsage extends AbstractStatsBase { if (primaryOrSplit) { // We have a possible update on the primary apk usage. Merge // isUsedByOtherApps information and return if there was an update. - return packageUseInfo.merge(isUsedByOtherApps); + boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, + loadingPackageName, packageUseInfo.mLoadingPackages); + return packageUseInfo.merge(isUsedByOtherApps) || updateLoadingPackages; } else { DexUseInfo newData = new DexUseInfo( isUsedByOtherApps, ownerUserId, loaderIsa); + boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, + loadingPackageName, newData.mLoadingPackages); + DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); if (existingData == null) { // It's the first time we see this dex file. @@ -138,7 +159,7 @@ public class PackageDexUsage extends AbstractStatsBase { } // Merge the information into the existing data. // Returns true if there was an update. - return existingData.merge(newData); + return existingData.merge(newData) || updateLoadingPackages; } } } @@ -185,15 +206,21 @@ public class PackageDexUsage extends AbstractStatsBase { * * file_magic_version * package_name_1 + * @ loading_package_1_1, loading_package_1_2... * #dex_file_path_1_1 + * @ loading_package_1_1_1, loading_package_1_1_2... * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 * #dex_file_path_1_2 + * @ loading_package_1_2_1, loading_package_1_2_2... * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 * ... * package_name_2 + * @ loading_package_2_1, loading_package_2_1_2... * #dex_file_path_2_1 + * @ loading_package_2_1_1, loading_package_2_1_2... * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2 * #dex_file_path_2_2, + * @ loading_package_2_2_1, loading_package_2_2_2... * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2 * ... */ @@ -214,12 +241,17 @@ public class PackageDexUsage extends AbstractStatsBase { fpw.println(String.join(SPLIT_CHAR, packageName, writeBoolean(packageUseInfo.mIsUsedByOtherApps))); + fpw.println(LOADING_PACKAGE_CHAR + + String.join(SPLIT_CHAR, packageUseInfo.mLoadingPackages)); // Write dex file lines. for (Map.Entry dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { String dexPath = dEntry.getKey(); DexUseInfo dexUseInfo = dEntry.getValue(); fpw.println(DEX_LINE_CHAR + dexPath); + fpw.println(LOADING_PACKAGE_CHAR + + String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages)); + fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), writeBoolean(dexUseInfo.mIsUsedByOtherApps))); for (String isa : dexUseInfo.mLoaderIsas) { @@ -252,6 +284,7 @@ public class PackageDexUsage extends AbstractStatsBase { BufferedReader in = new BufferedReader(reader); // Read header, do version check. String versionLine = in.readLine(); + int version; if (versionLine == null) { throw new IllegalStateException("No version line found."); } else { @@ -259,16 +292,16 @@ public class PackageDexUsage extends AbstractStatsBase { // TODO(calin): the caller is responsible to clear the file. throw new IllegalStateException("Invalid version line: " + versionLine); } - int version = Integer.parseInt( + version = Integer.parseInt( versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); - if (version != PACKAGE_DEX_USAGE_VERSION) { + if (!isSupportedVersion(version)) { throw new IllegalStateException("Unexpected version: " + version); } } - String s = null; - String currentPakage = null; - PackageUseInfo currentPakageData = null; + String s; + String currentPackage = null; + PackageUseInfo currentPackageData = null; Set supportedIsas = new HashSet<>(); for (String abi : Build.SUPPORTED_ABIS) { @@ -280,17 +313,21 @@ public class PackageDexUsage extends AbstractStatsBase { // We expect two lines for each dex entry: // #dexPaths // onwerUserId,isUsedByOtherApps,isa1,isa2 - if (currentPakage == null) { + if (currentPackage == null) { throw new IllegalStateException( "Malformed PackageDexUsage file. Expected package line before dex line."); } // First line is the dex path. String dexPath = s.substring(DEX_LINE_CHAR.length()); + + // In version 2 the second line contains the list of packages that loaded the file. + List loadingPackages = maybeReadLoadingPackages(in, version); + // Next line is the dex data. s = in.readLine(); if (s == null) { - throw new IllegalStateException("Could not fine dexUseInfo for line: " + s); + throw new IllegalStateException("Could not find dexUseInfo for line: " + s); } // We expect at least 3 elements (isUsedByOtherApps, userId, isa). @@ -301,6 +338,8 @@ public class PackageDexUsage extends AbstractStatsBase { int ownerUserId = Integer.parseInt(elems[0]); boolean isUsedByOtherApps = readBoolean(elems[1]); DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId); + dexUseInfo.mLoadingPackages.addAll(loadingPackages); + for (int i = 2; i < elems.length; i++) { String isa = elems[i]; if (supportedIsas.contains(isa)) { @@ -317,7 +356,7 @@ public class PackageDexUsage extends AbstractStatsBase { "unsupported isas. dexPath=" + dexPath); continue; } - currentPakageData.mDexUseInfoMap.put(dexPath, dexUseInfo); + currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); } else { // This is a package line. // We expect it to be: `packageName,isUsedByOtherApps`. @@ -325,10 +364,11 @@ public class PackageDexUsage extends AbstractStatsBase { if (elems.length != 2) { throw new IllegalStateException("Invalid PackageDexUsage line: " + s); } - currentPakage = elems[0]; - currentPakageData = new PackageUseInfo(); - currentPakageData.mIsUsedByOtherApps = readBoolean(elems[1]); - data.put(currentPakage, currentPakageData); + currentPackage = elems[0]; + currentPackageData = new PackageUseInfo(); + currentPackageData.mIsUsedByOtherApps = readBoolean(elems[1]); + currentPackageData.mLoadingPackages.addAll(maybeReadLoadingPackages(in, version)); + data.put(currentPackage, currentPackageData); } } @@ -339,6 +379,43 @@ public class PackageDexUsage extends AbstractStatsBase { } /** + * Reads the list of loading packages from the buffer {@parm in} if + * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}. + */ + private List maybeReadLoadingPackages(BufferedReader in, int version) + throws IOException { + if (version == PACKAGE_DEX_USAGE_VERSION) { + String line = in.readLine(); + if (line == null) { + throw new IllegalStateException("Could not find the loadingPackages line."); + } + // We expect that most of the times the list of loading packages will be empty. + if (line.length() == LOADING_PACKAGE_CHAR.length()) { + return Collections.emptyList(); + } else { + return Arrays.asList( + line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); + } + } else { + return Collections.emptyList(); + } + } + + /** + * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's + * not equal to {@param owningPackage} + */ + private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage, + Set loadingPackages) { + return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage); + } + + private boolean isSupportedVersion(int version) { + return version == PACKAGE_DEX_USAGE_VERSION || + version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1; + } + + /** * Syncs the existing data with the set of available packages by removing obsolete entries. */ public void syncData(Map> packageToUsersMap) { @@ -539,10 +616,13 @@ public class PackageDexUsage extends AbstractStatsBase { private boolean mIsUsedByOtherApps; // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). private final Map mDexUseInfoMap; + // Packages who load this dex file. + private final Set mLoadingPackages; public PackageUseInfo() { mIsUsedByOtherApps = false; mDexUseInfoMap = new HashMap<>(); + mLoadingPackages = new HashSet<>(); } // Creates a deep copy of the `other`. @@ -552,6 +632,7 @@ public class PackageDexUsage extends AbstractStatsBase { for (Map.Entry e : other.mDexUseInfoMap.entrySet()) { mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); } + mLoadingPackages = new HashSet<>(other.mLoadingPackages); } private boolean merge(boolean isUsedByOtherApps) { @@ -567,6 +648,10 @@ public class PackageDexUsage extends AbstractStatsBase { public Map getDexUseInfoMap() { return mDexUseInfoMap; } + + public Set getLoadingPackages() { + return mLoadingPackages; + } } /** @@ -576,6 +661,8 @@ public class PackageDexUsage extends AbstractStatsBase { private boolean mIsUsedByOtherApps; private final int mOwnerUserId; private final Set mLoaderIsas; + // Packages who load this dex file. + private final Set mLoadingPackages; public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) { this(isUsedByOtherApps, ownerUserId, null); @@ -588,6 +675,7 @@ public class PackageDexUsage extends AbstractStatsBase { if (loaderIsa != null) { mLoaderIsas.add(loaderIsa); } + mLoadingPackages = new HashSet<>(); } // Creates a deep copy of the `other`. @@ -595,13 +683,16 @@ public class PackageDexUsage extends AbstractStatsBase { mIsUsedByOtherApps = other.mIsUsedByOtherApps; mOwnerUserId = other.mOwnerUserId; mLoaderIsas = new HashSet<>(other.mLoaderIsas); + mLoadingPackages = new HashSet<>(other.mLoadingPackages); } private boolean merge(DexUseInfo dexUseInfo) { boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); - return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps); + boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); + return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps) || + updateLoadingPackages; } public boolean isUsedByOtherApps() { @@ -615,5 +706,9 @@ public class PackageDexUsage extends AbstractStatsBase { public Set getLoaderIsas() { return mLoaderIsas; } + + public Set getLoadingPackages() { + return mLoadingPackages; + } } } 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 2e99433149ea..e1ef41e71c34 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 @@ -72,25 +72,25 @@ public class PackageDexUsageTests { String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); mFooBaseUser0 = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, isa, false, true); + fooCodeDir + "base.apk", 0, isa, false, true, fooPackageName); mFooSplit1User0 = new TestData(fooPackageName, - fooCodeDir + "split-1.apk", 0, isa, false, true); + fooCodeDir + "split-1.apk", 0, isa, false, true, fooPackageName); mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, - fooCodeDir + "split-2.apk", 0, isa, true, true); + fooCodeDir + "split-2.apk", 0, isa, true, true, "used.by.other.com"); mFooSecondary1User0 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 0, isa, false, false); + fooDataDir + "sec-1.dex", 0, isa, false, false, fooPackageName); mFooSecondary1User1 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 1, isa, false, false); + fooDataDir + "sec-1.dex", 1, isa, false, false, fooPackageName); mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, - fooDataDir + "sec-2.dex", 0, isa, true, false); + fooDataDir + "sec-2.dex", 0, isa, true, false, "used.by.other.com"); mInvalidIsa = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true); + fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER"); String barPackageName = "com.google.bar"; String barCodeDir = "/data/app/com.google.bar/"; @@ -98,11 +98,11 @@ public class PackageDexUsageTests { String barDataDir1 = "/data/user/1/com.google.bar/"; mBarBaseUser0 = new TestData(barPackageName, - barCodeDir + "base.apk", 0, isa, false, true); + barCodeDir + "base.apk", 0, isa, false, true, barPackageName); mBarSecondary1User0 = new TestData(barPackageName, - barDataDir + "sec-1.dex", 0, isa, false, false); + barDataDir + "sec-1.dex", 0, isa, false, false, barPackageName); mBarSecondary2User1 = new TestData(barPackageName, - barDataDir1 + "sec-2.dex", 1, isa, false, false); + barDataDir1 + "sec-2.dex", 1, isa, false, false, barPackageName); } @Test @@ -319,7 +319,8 @@ public class PackageDexUsageTests { mFooSplit2UsedByOtherApps0.mOwnerUserId, mFooSplit2UsedByOtherApps0.mLoaderIsa, /*mIsUsedByOtherApps*/false, - mFooSplit2UsedByOtherApps0.mPrimaryOrSplit); + mFooSplit2UsedByOtherApps0.mPrimaryOrSplit, + mFooSplit2UsedByOtherApps0.mUsedBy); assertPackageDexUsage(noLongerUsedByOtherApps); } @@ -332,14 +333,65 @@ public class PackageDexUsageTests { assertFalse(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); } + @Test + public void testRecordDexFileUsers() { + PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); + Set users = new HashSet<>(Arrays.asList( + new String[] {"another.package.1"})); + Set usersExtra = new HashSet<>(Arrays.asList( + new String[] {"another.package.2", "another.package.3"})); + + assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, usersExtra)); + + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); + + packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); + // Verify that the users were recorded. + Set userAll = new HashSet<>(users); + userAll.addAll(usersExtra); + assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooBaseUser0, + mFooSecondary1User0); + } + + @Test + public void testRecordDexFileUsersNotTheOwningPackage() { + PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); + Set users = new HashSet<>(Arrays.asList( + new String[] {mFooBaseUser0.mPackageName,})); + Set usersExtra = new HashSet<>(Arrays.asList( + new String[] {"another.package.2", "another.package.3"})); + + assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooBaseUser0, 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, mFooBaseUser0, + mFooSecondary1User0); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { + assertPackageDexUsage(mPackageDexUsage, null, primary, secondaries); + } + + private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set users, + TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; - PackageUseInfo pInfo = mPackageDexUsage.getPackageUseInfo(packageName); + PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); // Check package use info assertNotNull(pInfo); assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps()); + if (users != null) { + assertEquals(pInfo.getLoadingPackages(), users); + } + Map dexUseInfoMap = pInfo.getDexUseInfoMap(); assertEquals(secondaries.length, dexUseInfoMap.size()); @@ -351,24 +403,43 @@ public class PackageDexUsageTests { assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId()); assertEquals(1, dInfo.getLoaderIsas().size()); assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa)); + if (users != null) { + assertEquals(dInfo.getLoadingPackages(), users); + } } } private boolean record(TestData testData) { return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, - testData.mPrimaryOrSplit); + testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mPrimaryOrSplit, testData.mUsedBy); + } + + private boolean record(PackageDexUsage packageDexUsage, TestData testData, Set users) { + boolean result = true; + for (String user : users) { + result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mPrimaryOrSplit, user); + } + return result; } private void writeAndReadBack() { + mPackageDexUsage = writeAndReadBack(mPackageDexUsage); + } + + private PackageDexUsage writeAndReadBack(PackageDexUsage packageDexUsage) { try { StringWriter writer = new StringWriter(); - mPackageDexUsage.write(writer); + packageDexUsage.write(writer); - mPackageDexUsage = new PackageDexUsage(); - mPackageDexUsage.read(new StringReader(writer.toString())); + PackageDexUsage newPackageDexUsage = new PackageDexUsage(); + newPackageDexUsage.read(new StringReader(writer.toString())); + return newPackageDexUsage; } catch (IOException e) { fail("Unexpected IOException: " + e.getMessage()); + return null; } } @@ -379,15 +450,17 @@ public class PackageDexUsageTests { private final String mLoaderIsa; private final boolean mUsedByOtherApps; private final boolean mPrimaryOrSplit; + private final String mUsedBy; private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { mPackageName = packageName; mDexFile = dexFile; mOwnerUserId = ownerUserId; mLoaderIsa = loaderIsa; mUsedByOtherApps = isUsedByOtherApps; mPrimaryOrSplit = primaryOrSplit; + mUsedBy = usedBy; } }