diff options
| author | 2017-11-01 22:35:16 +0000 | |
|---|---|---|
| committer | 2017-11-01 22:35:16 +0000 | |
| commit | f11984f9dac0a8624a99604d0b757aa9b8dc1cc3 (patch) | |
| tree | 550004e9c34c7f76996a2ef3cd539249f4b2ab8a | |
| parent | a47c4440fd4daa1c67c19191e89c9c6762e00ccc (diff) | |
| parent | 1eb700a545ddc521d31b82767345dd52a978f89f (diff) | |
Merge changes Ibf9e7b9e,I8031590c
am: 1eb700a545
Change-Id: I3bff20ab08a33f1607ec7b91abab2a77d7a3b22f
10 files changed, 427 insertions, 212 deletions
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 241d76f8a0ac..da6e26e17122 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 817fc903c39d..fadc32aa27e3 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -32,6 +32,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; @@ -123,7 +124,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; } @@ -131,7 +132,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); } @@ -145,21 +146,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<String> 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. @@ -183,6 +176,17 @@ public class PackageDexOptimizer { } } + final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary() + || packageUseInfo.isUsedByOtherApps(path); + 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 946f6cacd6af..317c73f8a88f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9620,17 +9620,19 @@ public class PackageManagerService extends IPackageManager.Stub Collection<PackageParser.Package> 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); } /** @@ -9756,7 +9758,7 @@ public class PackageManagerService extends IPackageManager.Stub public void shutdown() { mPackageUsage.writeNow(mPackages); mCompilerStats.writeNow(); - mDexManager.savePackageDexUsageNow(); + mDexManager.writePackageDexUsageNow(); } @Override @@ -18281,7 +18283,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); } @@ -25066,8 +25068,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 820b2cbded05..1e0ce7a215be 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -27,7 +27,6 @@ import com.android.internal.util.ArrayUtils; 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; @@ -144,9 +143,11 @@ 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, - 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? @@ -219,7 +220,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 947e01c49c59..62747547f320 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; @@ -81,6 +83,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<>(); @@ -297,6 +312,8 @@ public class DexManager { private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + Map<String, Set<String>> 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<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { @@ -306,25 +323,53 @@ 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<Integer> users = putIfAbsent( packageToUsersMap, pi.packageName, new HashSet<>()); users.add(userId); + + Set<String> 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); } /** * 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 +388,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 +432,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); @@ -519,23 +564,6 @@ public class DexManager { } /** - * 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. */ private DexSearchResult getDexPackage( @@ -593,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/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java index f57cf5e2b999..4fa47b5d0165 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/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 6ee26d32f0e0..a4a0a54b39d1 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<Void> { 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<Void> { // 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<Void> { 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<Void> { * * 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<Void> { // Write the package line. String packageName = pEntry.getKey(); PackageUseInfo packageUseInfo = pEntry.getValue(); - - fpw.println(String.join(SPLIT_CHAR, packageName, - writeBoolean(packageUseInfo.mIsUsedByOtherApps))); - fpw.println(LOADING_PACKAGE_CHAR + - String.join(SPLIT_CHAR, packageUseInfo.mLoadingPackages)); + fpw.println(packageName); + + // Write the code paths used by other apps. + for (Map.Entry<String, Set<String>> codeEntry : + packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { + String codePath = codeEntry.getKey(); + Set<String> 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<String, DexUseInfo> 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<Void> { } } - String s; + String line; String currentPackage = null; PackageUseInfo currentPackageData = null; @@ -332,8 +335,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { 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<Void> { "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<String> loadingPackages = maybeReadLoadingPackages(in, version); - // In version 3 the third line contains the class loader context. - String classLoaderContext = maybeReadClassLoaderContext(in, version); + // Line 1 is the dex path. + String dexPath = line.substring(DEX_LINE_CHAR.length()); - // 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<String> 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<Void> { 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<String> 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<Void> { */ 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<Void> { * 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<String> maybeReadLoadingPackages(BufferedReader in, int version) + private Set<String> 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<Void> { } // 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<String> 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<Void> { } 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<String, Set<Integer>> packageToUsersMap) { + /*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap, + Map<String, Set<String>> packageToCodePaths) { synchronized (mPackageUseInfoMap) { Iterator<Map.Entry<String, PackageUseInfo>> pIt = mPackageUseInfoMap.entrySet().iterator(); @@ -489,8 +511,26 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { dIt.remove(); } } - if (!packageUseInfo.mIsUsedByOtherApps - && packageUseInfo.mDexUseInfoMap.isEmpty()) { + + // Sync the code paths. + Set<String> codePaths = packageToCodePaths.get(packageName); + Iterator<Map.Entry<String, Set<String>>> 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<Void> { * 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<Void> { * @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<Void> { } // 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<Void> { * @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<Void> { 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<Void> { /** * Return all packages that contain records of secondary dex files. */ - public Set<String> getAllPackagesWithSecondaryDexFiles() { + /*package*/ Set<String> getAllPackagesWithSecondaryDexFiles() { Set<String> packages = new HashSet<>(); synchronized (mPackageUseInfoMap) { for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { @@ -639,15 +679,6 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { 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<Void> { * 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<String, Set<String>> mCodePathsUsedByOtherApps; // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). private final Map<String, DexUseInfo> mDexUseInfoMap; - // Packages who load this dex file. - private final Set<String> 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<String, Set<String>> e : other.mCodePathsUsedByOtherApps.entrySet()) { + mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); + } + mDexUseInfoMap = new HashMap<>(); for (Map.Entry<String, DexUseInfo> 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<String> 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<String, DexUseInfo> getDexUseInfoMap() { return mDexUseInfoMap; } - public Set<String> getLoadingPackages() { - return mLoadingPackages; + public Set<String> 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 e2dfb29be561..4db9a30a11ca 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,8 +116,7 @@ public class DexManagerTests { // Bar is used by others now and should be in our records PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -128,8 +127,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -141,8 +139,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, barSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); } @@ -165,8 +162,7 @@ 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()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(), pui.getDexUseInfoMap().size()); @@ -176,8 +172,7 @@ public class DexManagerTests { // Check foo usage. Should not be used by other app. pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -185,22 +180,22 @@ 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 - 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); - 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,8 +232,7 @@ public class DexManagerTests { // We should get back the right info. PackageUseInfo pui = getPackageUseInfo(newPackage); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); } @@ -254,8 +248,7 @@ public class DexManagerTests { notifyDexLoad(newPackage, newSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(newPackage); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -267,8 +260,7 @@ public class DexManagerTests { // Bar is used by others now and should be in our records. PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); // Notify that bar is updated. @@ -278,8 +270,7 @@ public class DexManagerTests { // The usedByOtherApps flag should be clear now. pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); } @Test @@ -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,9 +291,9 @@ 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()); + assertIsUsedByOtherApps(newSplits, pui, true); } @Test @@ -335,8 +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); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, true); 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,8 +370,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -395,8 +381,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts are unsupported. String[] expectedContexts = @@ -413,8 +398,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); @@ -422,8 +406,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - 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 = @@ -439,8 +422,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false); assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); // We expect that all the contexts are unsupported. String[] expectedContexts = @@ -484,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<String> codePaths, PackageUseInfo pui, + boolean isUsedByOtherApps) { + for (String codePath : codePaths) { + assertEquals(codePath, isUsedByOtherApps, pui.isUsedByOtherApps(codePath)); + } + } private void notifyDexLoad(TestData testData, List<String> dexPaths, int loaderUserId) { // By default, assume a single class loader in the chain. // This makes writing tests much easier. @@ -499,7 +492,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 1eb5552d533f..b64716c61418 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 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 3fc12b473429..69a148db8b63 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<String, Set<Integer>> packageToUsersMap = new HashMap<>(); packageToUsersMap.put(mBarSecondary2User1.mPackageName, new HashSet<>(Arrays.asList(mBarSecondary2User1.mOwnerUserId))); - mPackageDexUsage.syncData(packageToUsersMap); + Map<String, Set<String>> 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<String> 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<String> 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<String> users = new HashSet<>(Arrays.asList( - new String[] {mFooBaseUser0.mPackageName})); + new String[] {mFooSplit2UsedByOtherApps0.mPackageName})); Set<String> 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<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + Map<String, Set<String>> 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<String> 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<String, DexUseInfo> 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); + } } } |