From 3b74c41776da66562a68b12a0fed8d20b6952868 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Thu, 3 Aug 2017 19:48:37 -0700 Subject: [PATCH 1/2] Use PackageUseInfo in DexOptimizer Pass the PackageUseInfo directly to DexOptimizer and use it to detect if a package is used by other apps. Move the usage checks closer to dexopt so that they can be easily adapted when we add usage info for each of the app's code paths separately. This is a refactoring CLs to reduce the size and complexity of the upcoming CLs which record the usage info for each of the application splits. Bug: 64124380 Test: runtest -x services/tests/servicestests/src/com/android/server/pm/dex/* Change-Id: I8031590cdaff81ab1792ca19baddb6cb36dc021d --- .../android/server/pm/OtaDexoptService.java | 2 +- .../server/pm/PackageDexOptimizer.java | 26 ++++---- .../server/pm/PackageManagerService.java | 14 +++-- .../server/pm/PackageManagerServiceUtils.java | 8 +-- .../com/android/server/pm/dex/DexManager.java | 59 ++++++++++++------- .../android/server/pm/dex/DexoptOptions.java | 17 +++++- .../server/pm/dex/DexManagerTests.java | 49 ++++++--------- .../server/pm/dex/DexoptOptionsTests.java | 8 ++- 8 files changed, 104 insertions(+), 79 deletions(-) diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 241d76f8a0ac3..da6e26e17122b 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -318,7 +318,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { optimizer.performDexOpt(pkg, libraryDependencies, null /* ISAs */, null /* CompilerStats.PackageStats */, - mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), + mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(pkg.packageName), new DexoptOptions(pkg.packageName, compilationReason, DexoptOptions.DEXOPT_BOOT_COMPLETE)); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index dabd35c1fc148..0db1f0192d9da 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -31,6 +31,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.Installer.InstallerException; +import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.DexoptUtils; import com.android.server.pm.dex.PackageDexUsage; @@ -112,7 +113,7 @@ public class PackageDexOptimizer { */ int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries, String[] instructionSets, CompilerStats.PackageStats packageStats, - boolean isUsedByOtherApps, DexoptOptions options) { + PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) { if (!canOptimizePackage(pkg)) { return DEX_OPT_SKIPPED; } @@ -120,7 +121,7 @@ public class PackageDexOptimizer { final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid); try { return performDexOptLI(pkg, sharedLibraries, instructionSets, - packageStats, isUsedByOtherApps, options); + packageStats, packageUseInfo, options); } finally { releaseWakeLockLI(acquireTime); } @@ -134,21 +135,13 @@ public class PackageDexOptimizer { @GuardedBy("mInstallLock") private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries, String[] targetInstructionSets, CompilerStats.PackageStats packageStats, - boolean isUsedByOtherApps, DexoptOptions options) { + PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); final List paths = pkg.getAllCodePaths(); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, - options.getCompilerFilter(), isUsedByOtherApps); - final boolean profileUpdated = options.isCheckForProfileUpdates() && - isProfileUpdated(pkg, sharedGid, compilerFilter); - - // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. - final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete()); - // Get the class loader context dependencies. // For each code path in the package, this array contains the class loader context that // needs to be passed to dexopt in order to ensure correct optimizations. @@ -172,6 +165,17 @@ public class PackageDexOptimizer { } } + final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary() + || packageUseInfo.isUsedByOtherApps(); + final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, + options.getCompilerFilter(), isUsedByOtherApps); + final boolean profileUpdated = options.isCheckForProfileUpdates() && + isProfileUpdated(pkg, sharedGid, compilerFilter); + + // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct + // flags. + final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete()); + for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index aead98b900463..f0b318906d3d4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9868,17 +9868,19 @@ public class PackageManagerService extends IPackageManager.Stub Collection deps = findSharedNonSystemLibraries(p); final String[] instructionSets = getAppDexInstructionSets(p.applicationInfo); if (!deps.isEmpty()) { + DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(), + options.getCompilerFilter(), options.getSplitName(), + options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY); for (PackageParser.Package depPackage : deps) { // TODO: Analyze and investigate if we (should) profile libraries. pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets, getOrCreateCompilerPackageStats(depPackage), - true /* isUsedByOtherApps */, - options); + mDexManager.getPackageUseInfoOrDefault(depPackage.packageName), libraryOptions); } } return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, getOrCreateCompilerPackageStats(p), - mDexManager.isUsedByOtherApps(p.packageName), options); + mDexManager.getPackageUseInfoOrDefault(p.packageName), options); } /** @@ -18553,7 +18555,7 @@ public class PackageManagerService extends IPackageManager.Stub mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, null /* instructionSets */, getOrCreateCompilerPackageStats(pkg), - mDexManager.isUsedByOtherApps(pkg.packageName), + mDexManager.getPackageUseInfoOrDefault(pkg.packageName), dexoptOptions); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -25376,8 +25378,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); if (ps == null) { continue; } - PackageDexUsage.PackageUseInfo packageUseInfo = getDexManager().getPackageUseInfo( - pkg.packageName); + PackageDexUsage.PackageUseInfo packageUseInfo = + getDexManager().getPackageUseInfoOrDefault(pkg.packageName); if (PackageManagerServiceUtils .isUnusedSinceTimeInMillis(ps.firstInstallTime, currentTimeInMillis, downgradeTimeThresholdMillis, packageUseInfo, diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index a7031c9f31476..8e7e7889ec87b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -25,7 +25,6 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.app.AppGlobals; import android.content.Intent; -import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.content.pm.ResolveInfo; import android.os.Build; @@ -137,8 +136,9 @@ public class PackageManagerServiceUtils { sortTemp, packageManagerService); // Give priority to apps used by other apps. + DexManager dexManager = packageManagerService.getDexManager(); applyPackageFilter((pkg) -> - packageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), result, + dexManager.getPackageUseInfoOrDefault(pkg.packageName).isUsedByOtherApps(), result, remainingPkgs, sortTemp, packageManagerService); // Filter out packages that aren't recently used, add all remaining apps. @@ -209,12 +209,10 @@ public class PackageManagerServiceUtils { // If the app was active in background during the threshold period and was used // by other packages. - // If packageUseInfo is null, it can be said that the package was not used by other - // packages. boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis - latestPackageUseTimeInMillis) < thresholdTimeinMillis) - && (packageUseInfo != null && packageUseInfo.isUsedByOtherApps()); + && packageUseInfo.isUsedByOtherApps(); return !isActiveInBackgroundAndUsedByOtherPackages; } 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 947e01c49c592..352344b048c40 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -81,6 +81,19 @@ 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(IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock) { mPackageCodeLocationsCache = new HashMap<>(); @@ -321,10 +334,29 @@ public class DexManager { /** * Get the package dex usage for the given package name. - * @return the package data or null if there is no data available for this package. + * If there is no usage info the method will return a default {@code PackageUseInfo} with + * no data about secondary dex files and marked as not being used by other apps. + * + * Note that no use info means the package was not used or it was used but not by other apps. + * Also, note that right now we might prune packages which are not used by other apps. + * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try + * to access the package use. */ - public PackageUseInfo getPackageUseInfo(String packageName) { - return mPackageDexUsage.getPackageUseInfo(packageName); + public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { + PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); + return useInfo == null ? DEFAULT_USE_INFO : useInfo; + } + + /** + * Return whether or not the manager has usage information on the give package. + * + * Note that no use info means the package was not used or it was used but not by other apps. + * Also, note that right now we might prune packages which are not used by other apps. + * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try + * to access the package use. + */ + /*package*/ boolean hasInfoOnPackage(String packageName) { + return mPackageDexUsage.getPackageUseInfo(packageName) != null; } /** @@ -343,7 +375,7 @@ public class DexManager { ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) : mPackageDexOptimizer; String packageName = options.getPackageName(); - PackageUseInfo useInfo = getPackageUseInfo(packageName); + PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { Slog.d(TAG, "No secondary dex use for package:" + packageName); @@ -387,7 +419,7 @@ public class DexManager { * deleted, update the internal records and delete any generated oat files. */ public void reconcileSecondaryDexFiles(String packageName) { - PackageUseInfo useInfo = getPackageUseInfo(packageName); + PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { Slog.d(TAG, "No secondary dex use for package:" + packageName); @@ -518,23 +550,6 @@ public class DexManager { return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); } - /** - * Return true if the profiling data collected for the given app indicate - * that the apps's APK has been loaded by another app. - * Note that this returns false for all apps without any collected profiling data. - */ - public boolean isUsedByOtherApps(String packageName) { - PackageUseInfo useInfo = getPackageUseInfo(packageName); - if (useInfo == null) { - // No use info, means the package was not used or it was used but not by other apps. - // Note that right now we might prune packages which are not used by other apps. - // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try - // to access the package use. - return false; - } - return useInfo.isUsedByOtherApps(); - } - /** * Retrieves the package which owns the given dexPath. */ diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java index f57cf5e2b9996..4fa47b5d0165c 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java +++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java @@ -50,6 +50,12 @@ public final class DexoptOptions { // save disk space. public static final int DEXOPT_DOWNGRADE = 1 << 5; + // When set, dexopt will compile the dex file as a shared library even if it is not actually + // used by other apps. This is used to force the compilation or shared libraries declared + // with in the manifest with ''uses-library' before we have a chance to detect they are + // actually shared at runtime. + public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6; + // The name of package to optimize. private final String mPackageName; @@ -79,7 +85,8 @@ public final class DexoptOptions { DEXOPT_BOOT_COMPLETE | DEXOPT_ONLY_SECONDARY_DEX | DEXOPT_ONLY_SHARED_DEX | - DEXOPT_DOWNGRADE; + DEXOPT_DOWNGRADE | + DEXOPT_AS_SHARED_LIBRARY; if ((flags & (~validityMask)) != 0) { throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags)); } @@ -122,7 +129,15 @@ public final class DexoptOptions { return (mFlags & DEXOPT_DOWNGRADE) != 0; } + public boolean isDexoptAsSharedLibrary() { + return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0; + } + public String getSplitName() { return mSplitName; } + + public int getFlags() { + return mFlags; + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index e2dfb29be5610..90c4f44d86b22 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -106,7 +106,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); // Package is not used by others, so we should get nothing back. - assertNull(getPackageUseInfo(mFooUser0)); + assertNoUseInfo(mFooUser0); } @Test @@ -116,7 +116,6 @@ public class DexManagerTests { // Bar is used by others now and should be in our records PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertTrue(pui.isUsedByOtherApps()); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -128,7 +127,6 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); @@ -141,7 +139,6 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, barSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); @@ -165,7 +162,6 @@ public class DexManagerTests { // Check bar usage. Should be used by other app (for primary and barSecondaries). PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertTrue(pui.isUsedByOtherApps()); assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(), pui.getDexUseInfoMap().size()); @@ -176,7 +172,6 @@ public class DexManagerTests { // Check foo usage. Should not be used by other app. pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); @@ -185,14 +180,14 @@ public class DexManagerTests { @Test public void testPackageUseInfoNotFound() { // Assert we don't get back data we did not previously record. - assertNull(getPackageUseInfo(mFooUser0)); + assertNoUseInfo(mFooUser0); } @Test public void testInvalidIsa() { // Notifying with an invalid ISA should be ignored. notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); - assertNull(getPackageUseInfo(mInvalidIsa)); + assertNoUseInfo(mInvalidIsa); } @Test @@ -200,7 +195,7 @@ public class DexManagerTests { // Notifying about the load of a package which was previously not // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); - assertNull(getPackageUseInfo(mDoesNotExist)); + assertNoUseInfo(mDoesNotExist); } @Test @@ -208,7 +203,7 @@ public class DexManagerTests { // Bar from User1 tries to load secondary dex files from User0 Bar. // Request should be ignored. notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); - assertNull(getPackageUseInfo(mBarUser1)); + assertNoUseInfo(mBarUser1); } @Test @@ -217,7 +212,7 @@ public class DexManagerTests { // Note that the PackageManagerService already filters this out but we // still check that nothing goes unexpected in DexManager. notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); - assertNull(getPackageUseInfo(mBarUser1)); + assertNoUseInfo(mBarUser1); } @Test @@ -229,7 +224,7 @@ public class DexManagerTests { // Before we notify about the installation of the newPackage if mFoo // is trying to load something from it we should not find it. notifyDexLoad(mFooUser0, newSecondaries, mUser0); - assertNull(getPackageUseInfo(newPackage)); + assertNoUseInfo(newPackage); // Notify about newPackage install and let mFoo load its dexes. mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0); @@ -237,7 +232,6 @@ public class DexManagerTests { // We should get back the right info. PackageUseInfo pui = getPackageUseInfo(newPackage); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); @@ -254,7 +248,6 @@ public class DexManagerTests { notifyDexLoad(newPackage, newSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(newPackage); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); @@ -267,7 +260,6 @@ public class DexManagerTests { // Bar is used by others now and should be in our records. PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertTrue(pui.isUsedByOtherApps()); assertTrue(pui.getDexUseInfoMap().isEmpty()); @@ -278,7 +270,6 @@ public class DexManagerTests { // The usedByOtherApps flag should be clear now. pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); } @@ -291,8 +282,7 @@ public class DexManagerTests { // We shouldn't find yet the new split as we didn't notify the package update. notifyDexLoad(mFooUser0, newSplits, mUser0); - PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNull(pui); + assertNoUseInfo(mBarUser0); // Notify that bar is updated. splitSourceDirs will contain the updated path. mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), @@ -301,7 +291,7 @@ public class DexManagerTests { // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. notifyDexLoad(mFooUser0, newSplits, mUser0); - pui = getPackageUseInfo(mBarUser0); + PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertNotNull(pui); assertTrue(pui.isUsedByOtherApps()); } @@ -335,7 +325,6 @@ public class DexManagerTests { // Foo should still be around since it's used by other apps but with no // secondary dex info. PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); assertTrue(pui.isUsedByOtherApps()); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -350,8 +339,7 @@ public class DexManagerTests { // Foo should not be around since all its secondary dex info were deleted // and it is not used by other apps. - PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNull(pui); + assertNoUseInfo(mFooUser0); } @Test @@ -363,8 +351,7 @@ public class DexManagerTests { mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), UserHandle.USER_ALL); // Bar should not be around since it was removed for all users. - PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNull(pui); + assertNoUseInfo(mBarUser0); } @Test @@ -373,7 +360,7 @@ public class DexManagerTests { // Load a dex file from framework. notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); // The dex file should not be recognized as a package. - assertNull(mDexManager.getPackageUseInfo(frameworkDex)); + assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); } @Test @@ -383,7 +370,6 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); @@ -395,7 +381,6 @@ public class DexManagerTests { notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts are unsupported. @@ -413,7 +398,6 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); @@ -422,7 +406,6 @@ public class DexManagerTests { notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts to be changed to variable now. @@ -439,7 +422,6 @@ public class DexManagerTests { notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); - assertNotNull(pui); assertFalse(pui.isUsedByOtherApps()); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts are unsupported. @@ -499,7 +481,12 @@ public class DexManagerTests { } private PackageUseInfo getPackageUseInfo(TestData testData) { - return mDexManager.getPackageUseInfo(testData.mPackageInfo.packageName); + assertTrue(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName)); + return mDexManager.getPackageUseInfoOrDefault(testData.mPackageInfo.packageName); + } + + private void assertNoUseInfo(TestData testData) { + assertFalse(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName)); } private static PackageInfo getMockPackageInfo(String packageName, int userId) { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index 1eb5552d533f5..b64716c614186 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -62,7 +62,8 @@ public class DexoptOptionsTests { DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | DexoptOptions.DEXOPT_ONLY_SHARED_DEX | - DexoptOptions.DEXOPT_DOWNGRADE; + DexoptOptions.DEXOPT_DOWNGRADE | + DexoptOptions.DEXOPT_AS_SHARED_LIBRARY; DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags); assertEquals(mPackageName, opt.getPackageName()); @@ -74,6 +75,7 @@ public class DexoptOptionsTests { assertTrue(opt.isDexoptOnlySharedDex()); assertTrue(opt.isDowngrade()); assertTrue(opt.isForce()); + assertTrue(opt.isDexoptAsSharedLibrary()); } @Test @@ -89,7 +91,7 @@ public class DexoptOptionsTests { PackageManagerService.REASON_INSTALL, PackageManagerService.REASON_BACKGROUND_DEXOPT, PackageManagerService.REASON_AB_OTA, - PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE}; + PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE,}; for (int reason : reasons) { DexoptOptions opt = new DexoptOptions(mPackageName, reason, flags); @@ -102,6 +104,7 @@ public class DexoptOptionsTests { assertFalse(opt.isDexoptOnlySharedDex()); assertFalse(opt.isDowngrade()); assertTrue(opt.isForce()); + assertFalse(opt.isDexoptAsSharedLibrary()); } } @@ -119,6 +122,7 @@ public class DexoptOptionsTests { assertFalse(opt.isDexoptOnlySharedDex()); assertFalse(opt.isDowngrade()); assertTrue(opt.isForce()); + assertFalse(opt.isDexoptAsSharedLibrary()); } @Test From 52a452cf685c56dc6872dbb19e822736484f672f Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Fri, 4 Aug 2017 01:42:17 -0700 Subject: [PATCH 2/2] Record usage information per split Increase the granularity of usage information to store data on each split separately. Now, splits get their own useByOtherApps flag and can be compiled speed-profile when only the primary apk is loaded by other apps. Bug: 64124380 Test: runtest -x services/tests/servicestests/src/com/android/server/pm/dex/* Change-Id: Ibf9e7b9e67db9c6f0f45dc695bce8fbeb7be20ae --- .../server/pm/PackageDexOptimizer.java | 2 +- .../server/pm/PackageManagerService.java | 2 +- .../server/pm/PackageManagerServiceUtils.java | 7 +- .../com/android/server/pm/dex/DexManager.java | 21 +- .../server/pm/dex/PackageDexUsage.java | 277 +++++++++++------- .../server/pm/dex/DexManagerTests.java | 45 +-- .../server/pm/dex/PackageDexUsageTests.java | 108 ++++++- 7 files changed, 325 insertions(+), 137 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 0db1f0192d9da..b72abc79ed196 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -166,7 +166,7 @@ public class PackageDexOptimizer { } final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary() - || packageUseInfo.isUsedByOtherApps(); + || packageUseInfo.isUsedByOtherApps(path); final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, options.getCompilerFilter(), isUsedByOtherApps); final boolean profileUpdated = options.isCheckForProfileUpdates() && diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f0b318906d3d4..e2f5f97454eb1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -10006,7 +10006,7 @@ public class PackageManagerService extends IPackageManager.Stub public void shutdown() { mPackageUsage.writeNow(mPackages); mCompilerStats.writeNow(); - mDexManager.savePackageDexUsageNow(); + mDexManager.writePackageDexUsageNow(); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 8e7e7889ec87b..25fef0a0ce31b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -138,8 +138,9 @@ public class PackageManagerServiceUtils { // Give priority to apps used by other apps. DexManager dexManager = packageManagerService.getDexManager(); applyPackageFilter((pkg) -> - dexManager.getPackageUseInfoOrDefault(pkg.packageName).isUsedByOtherApps(), result, - remainingPkgs, sortTemp, packageManagerService); + dexManager.getPackageUseInfoOrDefault(pkg.packageName) + .isAnyCodePathUsedByOtherApps(), + result, remainingPkgs, sortTemp, packageManagerService); // Filter out packages that aren't recently used, add all remaining apps. // TODO: add a property to control this? @@ -212,7 +213,7 @@ public class PackageManagerServiceUtils { boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis - latestPackageUseTimeInMillis) < thresholdTimeinMillis) - && packageUseInfo.isUsedByOtherApps(); + && packageUseInfo.isAnyCodePathUsedByOtherApps(); return !isActiveInBackgroundAndUsedByOtherPackages; } 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 352344b048c40..62747547f320d 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -36,6 +36,8 @@ import com.android.server.pm.PackageManagerServiceCompilerMapping; import java.io.File; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.HashMap; import java.util.HashSet; @@ -310,6 +312,8 @@ public class DexManager { private void loadInternal(Map> existingPackages) { Map> packageToUsersMap = new HashMap<>(); + Map> packageToCodePaths = new HashMap<>(); + // Cache the code locations for the installed packages. This allows for // faster lookups (no locks) when finding what package owns the dex file. for (Map.Entry> entry : existingPackages.entrySet()) { @@ -319,17 +323,26 @@ public class DexManager { // Cache the code locations. cachePackageInfo(pi, userId); - // Cache a map from package name to the set of user ids who installed the package. + // Cache two maps: + // - from package name to the set of user ids who installed the package. + // - from package name to the set of code paths. // We will use it to sync the data and remove obsolete entries from // mPackageDexUsage. Set users = putIfAbsent( packageToUsersMap, pi.packageName, new HashSet<>()); users.add(userId); + + Set codePaths = putIfAbsent( + packageToCodePaths, pi.packageName, new HashSet<>()); + codePaths.add(pi.applicationInfo.sourceDir); + if (pi.applicationInfo.splitSourceDirs != null) { + Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs); + } } } mPackageDexUsage.read(); - mPackageDexUsage.syncData(packageToUsersMap); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); } /** @@ -608,9 +621,9 @@ public class DexManager { } /** - * Saves the in-memory package dex usage to disk right away. + * Writes the in-memory package dex usage to disk right away. */ - public void savePackageDexUsageNow() { + public void writePackageDexUsageNow() { mPackageDexUsage.writeNow(); } 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 6ee26d32f0e01..a4a0a54b39d12 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -35,6 +35,7 @@ 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; @@ -53,17 +54,21 @@ import libcore.util.Objects; public class PackageDexUsage extends AbstractStatsBase { private final static String TAG = "PackageDexUsage"; - // The last version update: add class loader contexts for secondary dex files. - private final static int PACKAGE_DEX_USAGE_VERSION = 3; // We support previous version to ensure that the usage list remains valid cross OTAs. private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1; - // Version 2 added the list of packages that load the dex files. + // Version 2 added: + // - the list of packages that load the dex files + // - class loader contexts for secondary dex files + // - usage for all code paths (including splits) private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2 = 2; + private final static int PACKAGE_DEX_USAGE_VERSION = PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; + private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; private final static String SPLIT_CHAR = ","; + private final static String CODE_PATH_LINE_CHAR = "+"; private final static String DEX_LINE_CHAR = "#"; private final static String LOADING_PACKAGE_CHAR = "@"; @@ -130,9 +135,8 @@ public class PackageDexUsage extends AbstractStatsBase { // 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.mIsUsedByOtherApps = isUsedByOtherApps; - maybeAddLoadingPackage(owningPackageName, loadingPackageName, - packageUseInfo.mLoadingPackages); + packageUseInfo.mergeCodePathUsedByOtherApps(dexPath, isUsedByOtherApps, + owningPackageName, 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. @@ -149,9 +153,8 @@ 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. - boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, - loadingPackageName, packageUseInfo.mLoadingPackages); - return packageUseInfo.merge(isUsedByOtherApps) || updateLoadingPackages; + return packageUseInfo.mergeCodePathUsedByOtherApps( + dexPath, isUsedByOtherApps, owningPackageName, loadingPackageName); } else { DexUseInfo newData = new DexUseInfo( isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); @@ -230,22 +233,18 @@ public class PackageDexUsage extends AbstractStatsBase { * * file_magic_version * package_name_1 + * +code_path1 * @ loading_package_1_1, loading_package_1_2... + * +code_path2 + * @ loading_package_2_1, loading_package_2_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 + * @ loading_package_1_1_1, loading_package_1_1_2... + * class_loader_context_1_1 * #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 + * @ loading_package_1_2_1, loading_package_1_2_2... + * class_loader_context_1_2 * ... */ /* package */ void write(Writer out) { @@ -262,27 +261,31 @@ public class PackageDexUsage extends AbstractStatsBase { // Write the package line. String packageName = pEntry.getKey(); PackageUseInfo packageUseInfo = pEntry.getValue(); + fpw.println(packageName); - fpw.println(String.join(SPLIT_CHAR, packageName, - writeBoolean(packageUseInfo.mIsUsedByOtherApps))); - fpw.println(LOADING_PACKAGE_CHAR + - String.join(SPLIT_CHAR, packageUseInfo.mLoadingPackages)); + // Write the code paths used by other apps. + for (Map.Entry> codeEntry : + packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { + String codePath = codeEntry.getKey(); + Set loadingPackages = codeEntry.getValue(); + fpw.println(CODE_PATH_LINE_CHAR + codePath); + fpw.println(LOADING_PACKAGE_CHAR + String.join(SPLIT_CHAR, loadingPackages)); + } // 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.println(dexUseInfo.getClassLoaderContext()); - fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), - writeBoolean(dexUseInfo.mIsUsedByOtherApps))); + writeBoolean(dexUseInfo.mIsUsedByOtherApps))); for (String isa : dexUseInfo.mLoaderIsas) { fpw.print(SPLIT_CHAR + isa); } fpw.println(); + fpw.println(LOADING_PACKAGE_CHAR + + String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages)); + fpw.println(dexUseInfo.getClassLoaderContext()); } } fpw.flush(); @@ -324,7 +327,7 @@ public class PackageDexUsage extends AbstractStatsBase { } } - String s; + String line; String currentPackage = null; PackageUseInfo currentPackageData = null; @@ -332,8 +335,8 @@ public class PackageDexUsage extends AbstractStatsBase { for (String abi : Build.SUPPORTED_ABIS) { supportedIsas.add(VMRuntime.getInstructionSet(abi)); } - while ((s = in.readLine()) != null) { - if (s.startsWith(DEX_LINE_CHAR)) { + while ((line = in.readLine()) != null) { + if (line.startsWith(DEX_LINE_CHAR)) { // This is the start of the the dex lines. // We expect 4 lines for each dex entry: // #dexPaths @@ -345,25 +348,23 @@ public class PackageDexUsage extends AbstractStatsBase { "Malformed PackageDexUsage file. Expected package line before dex line."); } - // First line is the dex path. - String dexPath = s.substring(DEX_LINE_CHAR.length()); + // Line 1 is the dex path. + String dexPath = line.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); - // In version 3 the third line contains the class loader context. - String classLoaderContext = maybeReadClassLoaderContext(in, version); - - // Next line is the dex data. - s = in.readLine(); - if (s == null) { + // Line 2 is the dex data: (userId, isUsedByOtherApps, isa). + line = in.readLine(); + if (line == null) { throw new IllegalStateException("Could not find dexUseInfo line"); } - - // We expect at least 3 elements (isUsedByOtherApps, userId, isa). - String[] elems = s.split(SPLIT_CHAR); + String[] elems = line.split(SPLIT_CHAR); if (elems.length < 3) { - throw new IllegalStateException("Invalid PackageDexUsage line: " + s); + throw new IllegalStateException("Invalid PackageDexUsage line: " + line); } + + // In version 2 we added the loading packages and class loader context. + Set loadingPackages = maybeReadLoadingPackages(in, version); + String classLoaderContext = maybeReadClassLoaderContext(in, version); + int ownerUserId = Integer.parseInt(elems[0]); boolean isUsedByOtherApps = readBoolean(elems[1]); DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId, @@ -386,17 +387,35 @@ public class PackageDexUsage extends AbstractStatsBase { continue; } currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); + } else if (line.startsWith(CODE_PATH_LINE_CHAR)) { + // This is a code path used by other apps line. + if (version < PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { + throw new IllegalArgumentException("Unexpected code path line when parsing " + + "PackageDexUseData: " + line); + } + + // Expects 2 lines: + // +code_paths + // @loading_packages + String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); + Set loadingPackages = maybeReadLoadingPackages(in, version); + currentPackageData.mCodePathsUsedByOtherApps.put(codePath, loadingPackages); } else { // This is a package line. - // We expect it to be: `packageName,isUsedByOtherApps`. - String[] elems = s.split(SPLIT_CHAR); - if (elems.length != 2) { - throw new IllegalStateException("Invalid PackageDexUsage line: " + s); + if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { + currentPackage = line; + currentPackageData = new PackageUseInfo(); + } else { + // Old version (<2) + // We expect it to be: `packageName,isUsedByOtherApps`. + String[] elems = line.split(SPLIT_CHAR); + if (elems.length != 2) { + throw new IllegalStateException("Invalid PackageDexUsage line: " + line); + } + currentPackage = elems[0]; + currentPackageData = new PackageUseInfo(); + currentPackageData.mUsedByOtherAppsBeforeUpgrade = readBoolean(elems[1]); } - currentPackage = elems[0]; - currentPackageData = new PackageUseInfo(); - currentPackageData.mIsUsedByOtherApps = readBoolean(elems[1]); - currentPackageData.mLoadingPackages.addAll(maybeReadLoadingPackages(in, version)); data.put(currentPackage, currentPackageData); } } @@ -413,7 +432,7 @@ public class PackageDexUsage extends AbstractStatsBase { */ private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { String context = null; - if (version == PACKAGE_DEX_USAGE_VERSION) { + if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { context = in.readLine(); if (context == null) { throw new IllegalStateException("Could not find the classLoaderContext line."); @@ -429,7 +448,7 @@ public class PackageDexUsage extends AbstractStatsBase { * Reads the list of loading packages from the buffer {@code in} if * {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}. */ - private List maybeReadLoadingPackages(BufferedReader in, int version) + private Set maybeReadLoadingPackages(BufferedReader in, int version) throws IOException { if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { String line = in.readLine(); @@ -438,13 +457,15 @@ public class PackageDexUsage extends AbstractStatsBase { } // 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(); + return Collections.emptySet(); } else { - return Arrays.asList( + Set result = new HashSet<>(); + Collections.addAll(result, line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); + return result; } } else { - return Collections.emptyList(); + return Collections.emptySet(); } } @@ -458,14 +479,15 @@ public class PackageDexUsage extends AbstractStatsBase { } private boolean isSupportedVersion(int version) { - return version == PACKAGE_DEX_USAGE_VERSION || - version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1; + return version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 + || version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; } /** * Syncs the existing data with the set of available packages by removing obsolete entries. */ - public void syncData(Map> packageToUsersMap) { + /*package*/ void syncData(Map> packageToUsersMap, + Map> packageToCodePaths) { synchronized (mPackageUseInfoMap) { Iterator> pIt = mPackageUseInfoMap.entrySet().iterator(); @@ -489,8 +511,26 @@ public class PackageDexUsage extends AbstractStatsBase { dIt.remove(); } } - if (!packageUseInfo.mIsUsedByOtherApps - && packageUseInfo.mDexUseInfoMap.isEmpty()) { + + // Sync the code paths. + Set codePaths = packageToCodePaths.get(packageName); + Iterator>> codeIt = + packageUseInfo.mCodePathsUsedByOtherApps.entrySet().iterator(); + while (codeIt.hasNext()) { + if (!codePaths.contains(codeIt.next().getKey())) { + codeIt.remove(); + } + } + + // In case the package was marked as used by other apps in a previous version + // propagate the flag to all the code paths. + // See mUsedByOtherAppsBeforeUpgrade docs on why it is important to do it. + if (packageUseInfo.mUsedByOtherAppsBeforeUpgrade) { + for (String codePath : codePaths) { + packageUseInfo.mergeCodePathUsedByOtherApps(codePath, true, null, null); + } + } else if (!packageUseInfo.isAnyCodePathUsedByOtherApps() + && packageUseInfo.mDexUseInfoMap.isEmpty()) { // The package is not used by other apps and we removed all its dex files // records. Remove the entire package record as well. pIt.remove(); @@ -504,14 +544,13 @@ public class PackageDexUsage extends AbstractStatsBase { * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. * @return true if the package usage info was updated. */ - public boolean clearUsedByOtherApps(String packageName) { + /*package*/ boolean clearUsedByOtherApps(String packageName) { synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); - if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) { + if (packageUseInfo == null) { return false; } - packageUseInfo.mIsUsedByOtherApps = false; - return true; + return packageUseInfo.clearCodePathUsedByOtherApps(); } } @@ -532,7 +571,7 @@ public class PackageDexUsage extends AbstractStatsBase { * @return true if the record was found and actually deleted, * false if the record doesn't exist */ - public boolean removeUserPackage(String packageName, int userId) { + /*package*/ boolean removeUserPackage(String packageName, int userId) { synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); if (packageUseInfo == null) { @@ -550,7 +589,8 @@ public class PackageDexUsage extends AbstractStatsBase { } // If no secondary dex info is left and the package is not used by other apps // remove the data since it is now useless. - if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) { + if (packageUseInfo.mDexUseInfoMap.isEmpty() + && !packageUseInfo.isAnyCodePathUsedByOtherApps()) { mPackageUseInfoMap.remove(packageName); updated = true; } @@ -564,7 +604,7 @@ public class PackageDexUsage extends AbstractStatsBase { * @return true if the record was found and actually deleted, * false if the record doesn't exist */ - public boolean removeDexFile(String packageName, String dexFile, int userId) { + /*package*/ boolean removeDexFile(String packageName, String dexFile, int userId) { synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); if (packageUseInfo == null) { @@ -586,7 +626,7 @@ public class PackageDexUsage extends AbstractStatsBase { return false; } - public PackageUseInfo getPackageUseInfo(String packageName) { + /*package*/ PackageUseInfo getPackageUseInfo(String packageName) { synchronized (mPackageUseInfoMap) { PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); // The useInfo contains a map for secondary dex files which could be modified @@ -601,7 +641,7 @@ public class PackageDexUsage extends AbstractStatsBase { /** * Return all packages that contain records of secondary dex files. */ - public Set getAllPackagesWithSecondaryDexFiles() { + /*package*/ Set getAllPackagesWithSecondaryDexFiles() { Set packages = new HashSet<>(); synchronized (mPackageUseInfoMap) { for (Map.Entry entry : mPackageUseInfoMap.entrySet()) { @@ -639,15 +679,6 @@ public class PackageDexUsage extends AbstractStatsBase { throw new IllegalArgumentException("Unknown bool encoding: " + bool); } - private boolean contains(int[] array, int elem) { - for (int i = 0; i < array.length; i++) { - if (elem == array[i]) { - return true; - } - } - return false; - } - public String dump() { StringWriter sw = new StringWriter(); write(sw); @@ -658,46 +689,94 @@ public class PackageDexUsage extends AbstractStatsBase { * Stores data on how a package and its dex files are used. */ public static class PackageUseInfo { - // This flag is for the primary and split apks. It is set to true whenever one of them - // is loaded by another app. - private boolean mIsUsedByOtherApps; + // 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; // 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; + + // Keeps track of whether or not this package was used by other apps before + // we upgraded to VERSION 4 which records the info for each code path separately. + // This is unwanted complexity but without it we risk to profile guide compile + // something that supposed to be shared. For example: + // 1) we determine that chrome is used by another app + // 2) we take an OTA which upgrades the way we keep track of usage data + // 3) chrome doesn't get used until the background job executes + // 4) as part of the backgound job we now think that chrome is not used by others + // and we speed-profile. + // 5) as a result the next time someone uses chrome it will extract from apk since + // the compiled code will be private. + private boolean mUsedByOtherAppsBeforeUpgrade; public PackageUseInfo() { - mIsUsedByOtherApps = false; + mCodePathsUsedByOtherApps = new HashMap<>(); mDexUseInfoMap = new HashMap<>(); - mLoadingPackages = new HashSet<>(); } // Creates a deep copy of the `other`. public PackageUseInfo(PackageUseInfo other) { - mIsUsedByOtherApps = other.mIsUsedByOtherApps; + mCodePathsUsedByOtherApps = new HashMap<>(); + for (Map.Entry> e : other.mCodePathsUsedByOtherApps.entrySet()) { + mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); + } + mDexUseInfoMap = new HashMap<>(); 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) { - boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; - mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps; - return oldIsUsedByOtherApps != this.mIsUsedByOtherApps; + 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); + if (loadingPackages == null) { + loadingPackages = new HashSet<>(); + mCodePathsUsedByOtherApps.put(codePath, loadingPackages); + newCodePath = true; + } + boolean newLoadingPackage = loadingPackage != null + && !loadingPackage.equals(owningPackageName) + && loadingPackages.add(loadingPackage); + return newCodePath || newLoadingPackage; } - public boolean isUsedByOtherApps() { - return mIsUsedByOtherApps; + public boolean isUsedByOtherApps(String codePath) { + return mCodePathsUsedByOtherApps.containsKey(codePath); } public Map getDexUseInfoMap() { return mDexUseInfoMap; } - public Set getLoadingPackages() { - return mLoadingPackages; + public Set getLoadingPackages(String codePath) { + return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); + } + + public boolean isAnyCodePathUsedByOtherApps() { + return !mCodePathsUsedByOtherApps.isEmpty(); + } + + /** + * Clears the usedByOtherApps markers from all code paths. + * Returns whether or not there was an update. + */ + /*package*/ boolean clearCodePathUsedByOtherApps() { + // Update mUsedByOtherAppsBeforeUpgrade as well to be consistent with + // the new data. This is not saved to disk so we don't need to return it. + mUsedByOtherAppsBeforeUpgrade = true; + + if (mCodePathsUsedByOtherApps.isEmpty()) { + return false; + } else { + mCodePathsUsedByOtherApps.clear(); + return true; + } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 90c4f44d86b22..4db9a30a11cab 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -116,7 +116,7 @@ public class DexManagerTests { // Bar is used by others now and should be in our records PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -127,7 +127,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -139,7 +139,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, barSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); } @@ -162,7 +162,7 @@ public class DexManagerTests { // Check bar usage. Should be used by other app (for primary and barSecondaries). PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(), pui.getDexUseInfoMap().size()); @@ -172,7 +172,7 @@ public class DexManagerTests { // Check foo usage. Should not be used by other app. pui = getPackageUseInfo(mFooUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -191,7 +191,7 @@ public class DexManagerTests { } @Test - public void testNotExistingPackate() { + public void testNotExistingPackage() { // Notifying about the load of a package which was previously not // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); @@ -232,7 +232,7 @@ public class DexManagerTests { // We should get back the right info. PackageUseInfo pui = getPackageUseInfo(newPackage); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); } @@ -248,7 +248,7 @@ public class DexManagerTests { notifyDexLoad(newPackage, newSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(newPackage); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -260,7 +260,7 @@ public class DexManagerTests { // Bar is used by others now and should be in our records. PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); // Notify that bar is updated. @@ -270,7 +270,7 @@ public class DexManagerTests { // The usedByOtherApps flag should be clear now. pui = getPackageUseInfo(mBarUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); } @Test @@ -293,7 +293,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, newSplits, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newSplits, pui, true); } @Test @@ -325,7 +325,7 @@ public class DexManagerTests { // Foo should still be around since it's used by other apps but with no // secondary dex info. PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -370,7 +370,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -381,7 +381,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts are unsupported. String[] expectedContexts = @@ -398,7 +398,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); @@ -406,7 +406,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts to be changed to variable now. String[] expectedContexts = @@ -422,7 +422,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts are unsupported. String[] expectedContexts = @@ -466,6 +466,17 @@ public class DexManagerTests { expectedContexts); } + private void assertIsUsedByOtherApps(TestData testData, PackageUseInfo pui, + boolean isUsedByOtherApps) { + assertIsUsedByOtherApps(testData.getBaseAndSplitDexPaths(), pui, isUsedByOtherApps); + } + + private void assertIsUsedByOtherApps(List codePaths, PackageUseInfo pui, + boolean isUsedByOtherApps) { + for (String codePath : codePaths) { + assertEquals(codePath, isUsedByOtherApps, pui.isUsedByOtherApps(codePath)); + } + } private void notifyDexLoad(TestData testData, List dexPaths, int loaderUserId) { // By default, assume a single class loader in the chain. // This makes writing tests much easier. 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 3fc12b4734292..69a148db8b638 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 @@ -21,6 +21,7 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import dalvik.system.VMRuntime; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -249,7 +250,10 @@ public class PackageDexUsageTests { Map> packageToUsersMap = new HashMap<>(); packageToUsersMap.put(mBarSecondary2User1.mPackageName, new HashSet<>(Arrays.asList(mBarSecondary2User1.mOwnerUserId))); - mPackageDexUsage.syncData(packageToUsersMap); + Map> packageToCodePaths = new HashMap<>(); + packageToCodePaths.put(mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); // Assert that only user 1 files are there. assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1); @@ -341,8 +345,8 @@ public class PackageDexUsageTests { 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, mFooSplit2UsedByOtherApps0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); @@ -351,7 +355,7 @@ public class PackageDexUsageTests { // Verify that the users were recorded. Set userAll = new HashSet<>(users); userAll.addAll(usersExtra); - assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooBaseUser0, + assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0, mFooSecondary1User0); } @@ -359,19 +363,19 @@ public class PackageDexUsageTests { public void testRecordDexFileUsersNotTheOwningPackage() { PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); Set users = new HashSet<>(Arrays.asList( - new String[] {mFooBaseUser0.mPackageName})); + new String[] {mFooSplit2UsedByOtherApps0.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, 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, mFooBaseUser0, + assertPackageDexUsage(packageDexUsageRecordUsers, usersExtra, mFooSplit2UsedByOtherApps0, mFooSecondary1User0); } @@ -428,7 +432,6 @@ public class PackageDexUsageTests { assertPackageDexUsage(null, mFooSecondary1User0); } - @Test public void testDexUsageClassLoaderContext() { final boolean isUsedByOtherApps = false; @@ -458,6 +461,80 @@ public class PackageDexUsageTests { assertFalse(unknownContext.isVariableClassLoaderContext()); } + @Test + public void testReadVersion1() { + 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__1\n" + + "com.google.foo,1\n" + + "#/data/user/0/com.google.foo/sec-1.dex\n" + + "0,0," + isa + "\n" + + "#/data/user/0/com.google.foo/sec-2.dex\n" + + "0,1," + isa + "\n" + + "com.google.bar,0\n" + + "#/data/user/0/com.google.bar/sec-1.dex\n" + + "0,0," + isa + "\n"; + + 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))); + + // Sync the data. + packageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + + // Update the class loaders to unknown before asserting if needed. Before version 2 we + // didn't have any. + String unknown = PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT; + TestData fooBaseUser0 = mFooBaseUser0.updateClassLoaderContext(unknown); + TestData fooSplit1User0 = mFooSplit1User0.updateClassLoaderContext(unknown); + TestData fooSplit2UsedByOtherApps0 = + mFooSplit2UsedByOtherApps0.updateClassLoaderContext(unknown); + TestData fooSecondary1User0 = mFooSecondary1User0.updateClassLoaderContext(unknown); + TestData fooSecondary2UsedByOtherApps0 = + mFooSecondary2UsedByOtherApps0.updateClassLoaderContext(unknown); + TestData barBaseUser0 = mBarBaseUser0.updateClassLoaderContext(unknown); + TestData barSecondary1User0 = mBarSecondary1User0.updateClassLoaderContext(unknown); + + // Assert foo code paths. Note that we ignore the users during upgrade. + final Set ignoredUsers = null; + assertPackageDexUsage(packageDexUsage, ignoredUsers, + fooSplit2UsedByOtherApps0, fooSecondary1User0, fooSecondary2UsedByOtherApps0); + // Because fooSplit2UsedByOtherApps0 is used by others, all the other code paths must + // share the same data. + assertPackageDexUsage(packageDexUsage, ignoredUsers, + fooSplit1User0.updateUseByOthers(true), + fooSecondary1User0, fooSecondary2UsedByOtherApps0); + assertPackageDexUsage(packageDexUsage, ignoredUsers, fooBaseUser0.updateUseByOthers(true), + fooSecondary1User0, fooSecondary2UsedByOtherApps0); + + // Assert bar code paths. Note that we ignore the users during upgrade. + assertPackageDexUsage(packageDexUsage, ignoredUsers, barBaseUser0, barSecondary1User0); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { assertPackageDexUsage(mPackageDexUsage, null, primary, secondaries); } @@ -470,9 +547,11 @@ public class PackageDexUsageTests { // Check package use info assertNotNull(pInfo); - assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps()); - if (users != null) { - assertEquals(pInfo.getLoadingPackages(), users); + if (primary != null) { + assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile)); + if (users != null) { + assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users); + } } Map dexUseInfoMap = pInfo.getDexUseInfoMap(); @@ -560,5 +639,10 @@ public class PackageDexUsageTests { return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mUsedByOtherApps, mPrimaryOrSplit, mUsedBy, newContext); } + + private TestData updateUseByOthers(boolean newUsedByOthers) { + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, newUsedByOthers, + mPrimaryOrSplit, mUsedBy, mClassLoaderContext); + } } }