Merge changes from topic 'info-per-split' into oc-mr1-dev

am: df421a8b47

Change-Id: I76e4499cf3ca4506f59f6c80c0f20faeda90f3e6
This commit is contained in:
Calin Juravle
2017-08-08 22:35:01 +00:00
committed by android-build-merger
10 changed files with 426 additions and 213 deletions

View File

@@ -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));

View File

@@ -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<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.
@@ -172,6 +165,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,

View File

@@ -9887,17 +9887,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);
}
/**
@@ -10023,7 +10025,7 @@ public class PackageManagerService extends IPackageManager.Stub
public void shutdown() {
mPackageUsage.writeNow(mPackages);
mCompilerStats.writeNow();
mDexManager.savePackageDexUsageNow();
mDexManager.writePackageDexUsageNow();
}
@Override
@@ -18572,7 +18574,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);
}
@@ -25409,8 +25411,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,

View File

@@ -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,9 +136,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?
@@ -209,12 +210,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.isAnyCodePathUsedByOtherApps();
return !isActiveInBackgroundAndUsedByOtherPackages;
}

View File

@@ -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);
@@ -518,23 +563,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.
*/
@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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(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<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());
// 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<String> 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<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;
}
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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);
}
}
}