Merge changes If274d038,I0788163b,Icdb9ff45,Ib546ed12 into rvc-dev

* changes:
  Ensure that loading packages can be extended
  Allow non-user packages to be kept in the use data list
  Allow PackageDexUsage to record code paths not used by others
  Remove support for PackageDexUsage version 1
This commit is contained in:
Calin Juravle
2020-04-06 06:40:51 +00:00
committed by Android (Google) Code Review
4 changed files with 333 additions and 315 deletions

View File

@@ -401,7 +401,8 @@ public class PackageDexOptimizer {
return DEX_OPT_FAILED; return DEX_OPT_FAILED;
} }
String classLoaderContext = null; String classLoaderContext = null;
if (dexUseInfo.isUnknownClassLoaderContext() || dexUseInfo.isVariableClassLoaderContext()) { if (dexUseInfo.isUnsupportedClassLoaderContext()
|| dexUseInfo.isVariableClassLoaderContext()) {
// If we have an unknown (not yet set), or a variable class loader chain. Just extract // If we have an unknown (not yet set), or a variable class loader chain. Just extract
// the dex file. // the dex file.
compilerFilter = "extract"; compilerFilter = "extract";

View File

@@ -45,6 +45,7 @@ import dalvik.system.VMRuntime;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -103,19 +104,6 @@ public class DexManager {
private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 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 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex
/**
* We do not record packages that have no secondary dex files or that are not used by other
* apps. This is an optimization to reduce the amount of data that needs to be written to
* disk (apps will not usually be shared so this trims quite a bit the number we record).
*
* To make this behaviour transparent to the callers which need use information on packages,
* DexManager will return this DEFAULT instance from
* {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and
* is marked as not being used by other apps. This reflects the intended behaviour when we don't
* find the package in the underlying data file.
*/
private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo,
Installer installer, Object installLock) { Installer installer, Object installLock) {
mContext = context; mContext = context;
@@ -194,6 +182,8 @@ public class DexManager {
// If the dex file is the primary apk (or a split) and not isUsedByOtherApps // If the dex file is the primary apk (or a split) and not isUsedByOtherApps
// do not record it. This case does not bring any new usable information // do not record it. This case does not bring any new usable information
// and can be safely skipped. // and can be safely skipped.
// Note this is just an optimization that makes things easier to read in the
// package-dex-use file since we don't need to pollute it with redundant info.
continue; continue;
} }
@@ -211,7 +201,7 @@ public class DexManager {
// async write to disk to make sure we don't loose the data in case of a reboot. // async write to disk to make sure we don't loose the data in case of a reboot.
if (mPackageDexUsage.record(searchResult.mOwningPackageName, if (mPackageDexUsage.record(searchResult.mOwningPackageName,
dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, dexPath, loaderUserId, loaderIsa, primaryOrSplit,
loadingAppInfo.packageName, classLoaderContext)) { loadingAppInfo.packageName, classLoaderContext)) {
mPackageDexUsage.maybeWriteAsync(); mPackageDexUsage.maybeWriteAsync();
} }
@@ -364,7 +354,9 @@ public class DexManager {
try { try {
mPackageDexUsage.read(); mPackageDexUsage.read();
mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); List<String> packagesToKeepDataAbout = new ArrayList<>();
mPackageDexUsage.syncData(
packageToUsersMap, packageToCodePaths, packagesToKeepDataAbout);
} catch (Exception e) { } catch (Exception e) {
mPackageDexUsage.clear(); mPackageDexUsage.clear();
Slog.w(TAG, "Exception while loading package dex usage. " Slog.w(TAG, "Exception while loading package dex usage. "
@@ -391,8 +383,17 @@ public class DexManager {
* to access the package use. * to access the package use.
*/ */
public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
// We do not record packages that have no secondary dex files or that are not used by other
// apps. This is an optimization to reduce the amount of data that needs to be written to
// disk (apps will not usually be shared so this trims quite a bit the number we record).
//
// To make this behaviour transparent to the callers which need use information on packages,
// DexManager will return this DEFAULT instance from
// {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files
// and is marked as not being used by other apps. This reflects the intended behaviour when
// we don't find the package in the underlying data file.
PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
return useInfo == null ? DEFAULT_USE_INFO : useInfo; return useInfo == null ? new PackageUseInfo(packageName) : useInfo;
} }
/** /**
@@ -542,7 +543,7 @@ public class DexManager {
// TODO(calin): questionable API in the presence of class loaders context. Needs amends as the // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
// compilation happening here will use a pessimistic context. // compilation happening here will use a pessimistic context.
public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
boolean isUsedByOtherApps, int userId) { boolean isSharedModule, int userId) {
// Find the owning package record. // Find the owning package record.
DexSearchResult searchResult = getDexPackage(info, dexPath, userId); DexSearchResult searchResult = getDexPackage(info, dexPath, userId);
@@ -559,11 +560,14 @@ public class DexManager {
// We found the package. Now record the usage for all declared ISAs. // We found the package. Now record the usage for all declared ISAs.
boolean update = false; boolean update = false;
// If this is a shared module set the loading package to an arbitrary package name
// so that we can mark that module as usedByOthers.
String loadingPackage = isSharedModule ? ".shared.module" : searchResult.mOwningPackageName;
for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) { for (String isa : getAppDexInstructionSets(info.primaryCpuAbi, info.secondaryCpuAbi)) {
boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, dexPath, userId, isa, /*primaryOrSplit*/ false,
searchResult.mOwningPackageName, searchResult.mOwningPackageName,
PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT);
update |= newUpdate; update |= newUpdate;
} }
if (update) { if (update) {

View File

@@ -39,10 +39,12 @@ import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@@ -51,25 +53,21 @@ import java.util.Set;
* Stat file which store usage information about dex files. * Stat file which store usage information about dex files.
*/ */
public class PackageDexUsage extends AbstractStatsBase<Void> { public class PackageDexUsage extends AbstractStatsBase<Void> {
private final static String TAG = "PackageDexUsage"; private static final String TAG = "PackageDexUsage";
// We support previous version to ensure that the usage list remains valid cross OTAs. // We are currently at version 2.
private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1; // Version 1 was introduced in Nougat and Version 2 in Oreo.
// Version 2 added: // We dropped version 1 support in R since all devices should have updated
// - the list of packages that load the dex files // already.
// - class loader contexts for secondary dex files private static final int PACKAGE_DEX_USAGE_VERSION = 2;
// - 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 static final String PACKAGE_DEX_USAGE_VERSION_HEADER =
private final static String PACKAGE_DEX_USAGE_VERSION_HEADER =
"PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__";
private final static String SPLIT_CHAR = ","; private static final String SPLIT_CHAR = ",";
private final static String CODE_PATH_LINE_CHAR = "+"; private static final String CODE_PATH_LINE_CHAR = "+";
private final static String DEX_LINE_CHAR = "#"; private static final String DEX_LINE_CHAR = "#";
private final static String LOADING_PACKAGE_CHAR = "@"; private static final String LOADING_PACKAGE_CHAR = "@";
// One of the things we record about dex files is the class loader context that was used to // One of the things we record about dex files is the class loader context that was used to
// load them. That should be stable but if it changes we don't keep track of variable contexts. // load them. That should be stable but if it changes we don't keep track of variable contexts.
@@ -77,10 +75,6 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
// skip optimizations on that dex files. // skip optimizations on that dex files.
/*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT = /*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT =
"=VariableClassLoaderContext="; "=VariableClassLoaderContext=";
// The markers used for unknown class loader contexts. This can happen if the dex file was
// recorded in a previous version and we didn't have a chance to update its usage.
/*package*/ static final String UNKNOWN_CLASS_LOADER_CONTEXT =
"=UnknownClassLoaderContext=";
// The marker used for unsupported class loader contexts (no longer written, may occur in old // The marker used for unsupported class loader contexts (no longer written, may occur in old
// files so discarded on read). Note: this matches // files so discarded on read). Note: this matches
@@ -126,7 +120,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
* has been seen before. * has been seen before.
*/ */
/* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId, /* package */ boolean record(String owningPackageName, String dexPath, int ownerUserId,
String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String loaderIsa, boolean primaryOrSplit,
String loadingPackageName, String classLoaderContext) { String loadingPackageName, String classLoaderContext) {
if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported");
@@ -135,20 +129,22 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
throw new IllegalArgumentException("Null classLoaderContext"); throw new IllegalArgumentException("Null classLoaderContext");
} }
if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) { if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) {
Slog.e(TAG, "Unsupported context?");
return false; return false;
} }
boolean isUsedByOtherApps = !owningPackageName.equals(loadingPackageName);
synchronized (mPackageUseInfoMap) { synchronized (mPackageUseInfoMap) {
PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName);
if (packageUseInfo == null) { if (packageUseInfo == null) {
// This is the first time we see the package. // This is the first time we see the package.
packageUseInfo = new PackageUseInfo(); packageUseInfo = new PackageUseInfo(owningPackageName);
if (primaryOrSplit) { if (primaryOrSplit) {
// If we have a primary or a split apk, set isUsedByOtherApps. // 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 // We do not need to record the loaderIsa or the owner because we compile
// primaries for all users and all ISAs. // primaries for all users and all ISAs.
packageUseInfo.mergeCodePathUsedByOtherApps(dexPath, isUsedByOtherApps, packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName);
owningPackageName, loadingPackageName);
} else { } else {
// For secondary dex files record the loaderISA and the owner. We'll need // For secondary dex files record the loaderISA and the owner. We'll need
// to know under which user to compile and for what ISA. // to know under which user to compile and for what ISA.
@@ -164,9 +160,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
// We already have data on this package. Amend it. // We already have data on this package. Amend it.
if (primaryOrSplit) { if (primaryOrSplit) {
// We have a possible update on the primary apk usage. Merge // We have a possible update on the primary apk usage. Merge
// isUsedByOtherApps information and return if there was an update. // dex path information and return if there was an update.
return packageUseInfo.mergeCodePathUsedByOtherApps( return packageUseInfo.mergePrimaryCodePaths(dexPath, loadingPackageName);
dexPath, isUsedByOtherApps, owningPackageName, loadingPackageName);
} else { } else {
DexUseInfo newData = new DexUseInfo( DexUseInfo newData = new DexUseInfo(
isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa);
@@ -281,7 +276,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
// Write the code paths used by other apps. // Write the code paths used by other apps.
for (Map.Entry<String, Set<String>> codeEntry : for (Map.Entry<String, Set<String>> codeEntry :
packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { packageUseInfo.mPrimaryCodePaths.entrySet()) {
String codePath = codeEntry.getKey(); String codePath = codeEntry.getKey();
Set<String> loadingPackages = codeEntry.getValue(); Set<String> loadingPackages = codeEntry.getValue();
fpw.println(CODE_PATH_LINE_CHAR + codePath); fpw.println(CODE_PATH_LINE_CHAR + codePath);
@@ -339,7 +334,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
version = Integer.parseInt( version = Integer.parseInt(
versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length()));
if (!isSupportedVersion(version)) { if (!isSupportedVersion(version)) {
throw new IllegalStateException("Unexpected version: " + version); Slog.w(TAG, "Unexpected package-dex-use version: " + version
+ ". Not reading from it");
return;
} }
} }
@@ -377,9 +374,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
throw new IllegalStateException("Invalid PackageDexUsage line: " + line); throw new IllegalStateException("Invalid PackageDexUsage line: " + line);
} }
// In version 2 we added the loading packages and class loader context. Set<String> loadingPackages = readLoadingPackages(in, version);
Set<String> loadingPackages = maybeReadLoadingPackages(in, version); String classLoaderContext = readClassLoaderContext(in, version);
String classLoaderContext = maybeReadClassLoaderContext(in, version);
if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(classLoaderContext)) { if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(classLoaderContext)) {
// We used to record use of unsupported class loaders, but we no longer do. // We used to record use of unsupported class loaders, but we no longer do.
@@ -410,34 +406,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
} }
currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo);
} else if (line.startsWith(CODE_PATH_LINE_CHAR)) { } 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: // Expects 2 lines:
// +code_paths // +code_paths
// @loading_packages // @loading_packages
String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); String codePath = line.substring(CODE_PATH_LINE_CHAR.length());
Set<String> loadingPackages = maybeReadLoadingPackages(in, version); Set<String> loadingPackages = readLoadingPackages(in, version);
currentPackageData.mCodePathsUsedByOtherApps.put(codePath, loadingPackages); currentPackageData.mPrimaryCodePaths.put(codePath, loadingPackages);
} else { } else {
// This is a package line. // This is a package line.
if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) {
currentPackage = line; currentPackage = line;
currentPackageData = new PackageUseInfo(); currentPackageData = new PackageUseInfo(currentPackage);
} 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]);
}
data.put(currentPackage, currentPackageData); data.put(currentPackage, currentPackageData);
} }
} }
@@ -449,47 +427,32 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
} }
/** /**
* Reads the class loader context encoding from the buffer {@code in} if * Reads the class loader context encoding from the buffer {@code in}.
* {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}.
*/ */
private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { private String readClassLoaderContext(BufferedReader in, int version) throws IOException {
String context = null; String context = in.readLine();
if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) {
context = in.readLine();
if (context == null) { if (context == null) {
throw new IllegalStateException("Could not find the classLoaderContext line."); throw new IllegalStateException("Could not find the classLoaderContext line.");
} }
} return context;
// The context might be empty if we didn't have the chance to update it after a version
// upgrade. In this case return the special marker so that we recognize this is an unknown
// context.
return context == null ? UNKNOWN_CLASS_LOADER_CONTEXT : context;
} }
/** /**
* Reads the list of loading packages from the buffer {@code in} if * Reads the list of loading packages from the buffer {@code in}.
* {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}.
*/ */
private Set<String> maybeReadLoadingPackages(BufferedReader in, int version) private Set<String> readLoadingPackages(BufferedReader in, int version)
throws IOException { throws IOException {
if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) {
String line = in.readLine(); String line = in.readLine();
if (line == null) { if (line == null) {
throw new IllegalStateException("Could not find the loadingPackages line."); throw new IllegalStateException("Could not find the loadingPackages line.");
} }
// We expect that most of the times the list of loading packages will be empty.
if (line.length() == LOADING_PACKAGE_CHAR.length()) {
return Collections.emptySet();
} else {
Set<String> result = new HashSet<>(); Set<String> result = new HashSet<>();
if (line.length() != LOADING_PACKAGE_CHAR.length()) {
Collections.addAll(result, Collections.addAll(result,
line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR));
}
return result; return result;
} }
} else {
return Collections.emptySet();
}
}
/** /**
* Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's
@@ -501,21 +464,26 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
} }
private boolean isSupportedVersion(int version) { private boolean isSupportedVersion(int version) {
return version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 return version == PACKAGE_DEX_USAGE_VERSION;
|| version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2;
} }
/** /**
* Syncs the existing data with the set of available packages by removing obsolete entries. * Syncs the existing data with the set of available packages by removing obsolete entries.
*/ */
/*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap, /*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap,
Map<String, Set<String>> packageToCodePaths) { Map<String, Set<String>> packageToCodePaths,
List<String> packagesToKeepDataAbout) {
synchronized (mPackageUseInfoMap) { synchronized (mPackageUseInfoMap) {
Iterator<Map.Entry<String, PackageUseInfo>> pIt = Iterator<Map.Entry<String, PackageUseInfo>> pIt =
mPackageUseInfoMap.entrySet().iterator(); mPackageUseInfoMap.entrySet().iterator();
while (pIt.hasNext()) { while (pIt.hasNext()) {
Map.Entry<String, PackageUseInfo> pEntry = pIt.next(); Map.Entry<String, PackageUseInfo> pEntry = pIt.next();
String packageName = pEntry.getKey(); String packageName = pEntry.getKey();
if (packagesToKeepDataAbout.contains(packageName)) {
// This is a package for which we should keep the data even if it's not
// in the list of user packages.
continue;
}
PackageUseInfo packageUseInfo = pEntry.getValue(); PackageUseInfo packageUseInfo = pEntry.getValue();
Set<Integer> users = packageToUsersMap.get(packageName); Set<Integer> users = packageToUsersMap.get(packageName);
if (users == null) { if (users == null) {
@@ -536,22 +504,31 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
// Sync the code paths. // Sync the code paths.
Set<String> codePaths = packageToCodePaths.get(packageName); Set<String> codePaths = packageToCodePaths.get(packageName);
Iterator<Map.Entry<String, Set<String>>> codeIt =
packageUseInfo.mCodePathsUsedByOtherApps.entrySet().iterator(); Iterator<Map.Entry<String, Set<String>>> recordedIt =
while (codeIt.hasNext()) { packageUseInfo.mPrimaryCodePaths.entrySet().iterator();
if (!codePaths.contains(codeIt.next().getKey())) { while (recordedIt.hasNext()) {
codeIt.remove(); Map.Entry<String, Set<String>> entry = recordedIt.next();
String recordedCodePath = entry.getKey();
if (!codePaths.contains(recordedCodePath)) {
// Clean up a non existing code path.
recordedIt.remove();
} else {
// Clean up a non existing loading package.
Set<String> recordedLoadingPackages = entry.getValue();
Iterator<String> recordedLoadingPackagesIt =
recordedLoadingPackages.iterator();
while (recordedLoadingPackagesIt.hasNext()) {
String recordedLoadingPackage = recordedLoadingPackagesIt.next();
if (!packagesToKeepDataAbout.contains(recordedLoadingPackage)
&& !packageToUsersMap.containsKey(recordedLoadingPackage)) {
recordedLoadingPackagesIt.remove();
}
}
} }
} }
// In case the package was marked as used by other apps in a previous version if (!packageUseInfo.isAnyCodePathUsedByOtherApps()
// 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()) { && packageUseInfo.mDexUseInfoMap.isEmpty()) {
// The package is not used by other apps and we removed all its dex files // The package is not used by other apps and we removed all its dex files
// records. Remove the entire package record as well. // records. Remove the entire package record as well.
@@ -712,35 +689,26 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
* Stores data on how a package and its dex files are used. * Stores data on how a package and its dex files are used.
*/ */
public static class PackageUseInfo { public static class PackageUseInfo {
// The name of the package this info belongs to.
private final String mPackageName;
// The app's code paths that are used by other apps. // The 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. // The key is the code path and the value is the set of loading packages.
private final Map<String, Set<String>> mCodePathsUsedByOtherApps; private final Map<String, Set<String>> mPrimaryCodePaths;
// Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa).
private final Map<String, DexUseInfo> mDexUseInfoMap; private final Map<String, DexUseInfo> mDexUseInfoMap;
// Keeps track of whether or not this package was used by other apps before /*package*/ PackageUseInfo(String packageName) {
// we upgraded to VERSION 4 which records the info for each code path separately. mPrimaryCodePaths = new HashMap<>();
// 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;
/*package*/ PackageUseInfo() {
mCodePathsUsedByOtherApps = new HashMap<>();
mDexUseInfoMap = new HashMap<>(); mDexUseInfoMap = new HashMap<>();
mPackageName = packageName;
} }
// Creates a deep copy of the `other`. // Creates a deep copy of the `other`.
private PackageUseInfo(PackageUseInfo other) { private PackageUseInfo(PackageUseInfo other) {
mCodePathsUsedByOtherApps = new HashMap<>(); mPackageName = other.mPackageName;
for (Map.Entry<String, Set<String>> e : other.mCodePathsUsedByOtherApps.entrySet()) { mPrimaryCodePaths = new HashMap<>();
mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); for (Map.Entry<String, Set<String>> e : other.mPrimaryCodePaths.entrySet()) {
mPrimaryCodePaths.put(e.getKey(), new HashSet<>(e.getValue()));
} }
mDexUseInfoMap = new HashMap<>(); mDexUseInfoMap = new HashMap<>();
@@ -749,28 +717,29 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
} }
} }
private boolean mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, private boolean mergePrimaryCodePaths(String codePath, String loadingPackage) {
String owningPackageName, String loadingPackage) { Set<String> loadingPackages = mPrimaryCodePaths.get(codePath);
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) { if (loadingPackages == null) {
loadingPackages = new HashSet<>(); loadingPackages = new HashSet<>();
mCodePathsUsedByOtherApps.put(codePath, loadingPackages); mPrimaryCodePaths.put(codePath, loadingPackages);
newCodePath = true;
} }
boolean newLoadingPackage = loadingPackage != null return loadingPackages.add(loadingPackage);
&& !loadingPackage.equals(owningPackageName)
&& loadingPackages.add(loadingPackage);
return newCodePath || newLoadingPackage;
} }
public boolean isUsedByOtherApps(String codePath) { public boolean isUsedByOtherApps(String codePath) {
return mCodePathsUsedByOtherApps.containsKey(codePath); if (mPrimaryCodePaths.containsKey(codePath)) {
Set<String> loadingPackages = mPrimaryCodePaths.get(codePath);
if (loadingPackages.contains(mPackageName)) {
// If the owning package is in the list then this code path
// is used by others if there are other packages in the list.
return loadingPackages.size() > 1;
} else {
// The owning package is not in the loading packages. So if
// the list is non-empty then the code path is used by others.
return !loadingPackages.isEmpty();
}
}
return false;
} }
public Map<String, DexUseInfo> getDexUseInfoMap() { public Map<String, DexUseInfo> getDexUseInfoMap() {
@@ -778,11 +747,11 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
} }
public Set<String> getLoadingPackages(String codePath) { public Set<String> getLoadingPackages(String codePath) {
return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); return mPrimaryCodePaths.getOrDefault(codePath, null);
} }
public boolean isAnyCodePathUsedByOtherApps() { public boolean isAnyCodePathUsedByOtherApps() {
return !mCodePathsUsedByOtherApps.isEmpty(); return !mPrimaryCodePaths.isEmpty();
} }
/** /**
@@ -790,17 +759,17 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
* Returns whether or not there was an update. * Returns whether or not there was an update.
*/ */
/*package*/ boolean clearCodePathUsedByOtherApps() { /*package*/ boolean clearCodePathUsedByOtherApps() {
// Update mUsedByOtherAppsBeforeUpgrade as well to be consistent with boolean updated = false;
// the new data. This is not saved to disk so we don't need to return it. List<String> retainOnlyOwningPackage = new ArrayList<>(1);
mUsedByOtherAppsBeforeUpgrade = true; retainOnlyOwningPackage.add(mPackageName);
for (Map.Entry<String, Set<String>> entry : mPrimaryCodePaths.entrySet()) {
if (mCodePathsUsedByOtherApps.isEmpty()) { // Remove or loading packages but the owning one.
return false; if (entry.getValue().retainAll(retainOnlyOwningPackage)) {
} else { updated = true;
mCodePathsUsedByOtherApps.clear();
return true;
} }
} }
return updated;
}
} }
/** /**
@@ -847,11 +816,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
String oldClassLoaderContext = mClassLoaderContext; String oldClassLoaderContext = mClassLoaderContext;
if (isUnknownOrUnsupportedContext(mClassLoaderContext)) { if (isUnsupportedContext(mClassLoaderContext)) {
// Can happen if we read a previous version.
mClassLoaderContext = dexUseInfo.mClassLoaderContext; mClassLoaderContext = dexUseInfo.mClassLoaderContext;
} else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext) } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
&& !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
// We detected a context change. // We detected a context change.
mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT;
} }
@@ -862,11 +829,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
|| !Objects.equals(oldClassLoaderContext, mClassLoaderContext); || !Objects.equals(oldClassLoaderContext, mClassLoaderContext);
} }
private static boolean isUnknownOrUnsupportedContext(String context) { private static boolean isUnsupportedContext(String context) {
// TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context);
// into UNSUPPORTED_CLASS_LOADER_CONTEXT.
return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context)
|| UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context);
} }
public boolean isUsedByOtherApps() { public boolean isUsedByOtherApps() {
@@ -887,10 +851,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
public String getClassLoaderContext() { return mClassLoaderContext; } public String getClassLoaderContext() { return mClassLoaderContext; }
public boolean isUnknownClassLoaderContext() { public boolean isUnsupportedClassLoaderContext() {
// The class loader context may be unknown if we loaded the data from a previous version return isUnsupportedContext(mClassLoaderContext);
// which didn't save the context.
return isUnknownOrUnsupportedContext(mClassLoaderContext);
} }
public boolean isVariableClassLoaderContext() { public boolean isVariableClassLoaderContext() {

View File

@@ -77,25 +77,25 @@ public class PackageDexUsageTests {
String fooDataDir = "/data/user/0/com.google.foo/"; String fooDataDir = "/data/user/0/com.google.foo/";
mFooBaseUser0 = new TestData(fooPackageName, mFooBaseUser0 = new TestData(fooPackageName,
fooCodeDir + "base.apk", 0, ISA, false, true, fooPackageName); fooCodeDir + "base.apk", 0, ISA, true, fooPackageName);
mFooSplit1User0 = new TestData(fooPackageName, mFooSplit1User0 = new TestData(fooPackageName,
fooCodeDir + "split-1.apk", 0, ISA, false, true, fooPackageName); fooCodeDir + "split-1.apk", 0, ISA, true, fooPackageName);
mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName,
fooCodeDir + "split-2.apk", 0, ISA, true, true, "used.by.other.com"); fooCodeDir + "split-2.apk", 0, ISA, true, "used.by.other.com");
mFooSecondary1User0 = new TestData(fooPackageName, mFooSecondary1User0 = new TestData(fooPackageName,
fooDataDir + "sec-1.dex", 0, ISA, false, false, fooPackageName); fooDataDir + "sec-1.dex", 0, ISA, false, fooPackageName);
mFooSecondary1User1 = new TestData(fooPackageName, mFooSecondary1User1 = new TestData(fooPackageName,
fooDataDir + "sec-1.dex", 1, ISA, false, false, fooPackageName); fooDataDir + "sec-1.dex", 1, ISA, false, fooPackageName);
mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName,
fooDataDir + "sec-2.dex", 0, ISA, true, false, "used.by.other.com"); fooDataDir + "sec-2.dex", 0, ISA, false, "used.by.other.com");
mInvalidIsa = new TestData(fooPackageName, mInvalidIsa = new TestData(fooPackageName,
fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER"); fooCodeDir + "base.apk", 0, "INVALID_ISA", true, "INALID_USER");
String barPackageName = "com.google.bar"; String barPackageName = "com.google.bar";
String barCodeDir = "/data/app/com.google.bar/"; String barCodeDir = "/data/app/com.google.bar/";
@@ -103,11 +103,11 @@ public class PackageDexUsageTests {
String barDataDir1 = "/data/user/1/com.google.bar/"; String barDataDir1 = "/data/user/1/com.google.bar/";
mBarBaseUser0 = new TestData(barPackageName, mBarBaseUser0 = new TestData(barPackageName,
barCodeDir + "base.apk", 0, ISA, false, true, barPackageName); barCodeDir + "base.apk", 0, ISA, true, barPackageName);
mBarSecondary1User0 = new TestData(barPackageName, mBarSecondary1User0 = new TestData(barPackageName,
barDataDir + "sec-1.dex", 0, ISA, false, false, barPackageName); barDataDir + "sec-1.dex", 0, ISA, false, barPackageName);
mBarSecondary2User1 = new TestData(barPackageName, mBarSecondary2User1 = new TestData(barPackageName,
barDataDir1 + "sec-2.dex", 1, ISA, false, false, barPackageName); barDataDir1 + "sec-2.dex", 1, ISA, false, barPackageName);
} }
@Test @Test
@@ -134,7 +134,9 @@ public class PackageDexUsageTests {
public void testRecordSplitPrimarySequence() { public void testRecordSplitPrimarySequence() {
// Assert new information. // Assert new information.
assertTrue(record(mFooBaseUser0)); assertTrue(record(mFooBaseUser0));
// Assert no new information. assertTrue(record(mFooSplit1User0));
// Assert no new information if we add again
assertFalse(record(mFooBaseUser0));
assertFalse(record(mFooSplit1User0)); assertFalse(record(mFooSplit1User0));
assertPackageDexUsage(mFooBaseUser0); assertPackageDexUsage(mFooBaseUser0);
@@ -192,7 +194,7 @@ public class PackageDexUsageTests {
for (int i = 1; i <= tooManyFiles; i++) { for (int i = 1; i <= tooManyFiles; i++) {
String fooPackageName = "com.google.foo"; String fooPackageName = "com.google.foo";
TestData testData = new TestData(fooPackageName, TestData testData = new TestData(fooPackageName,
"/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, false, "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false,
fooPackageName); fooPackageName);
if (i < tooManyFiles) { if (i < tooManyFiles) {
assertTrue("Adding " + testData.mDexFile, record(testData)); assertTrue("Adding " + testData.mDexFile, record(testData));
@@ -200,7 +202,11 @@ public class PackageDexUsageTests {
} else { } else {
assertFalse("Adding " + testData.mDexFile, record(testData)); assertFalse("Adding " + testData.mDexFile, record(testData));
} }
assertPackageDexUsage(mPackageDexUsage, null, null, expectedSecondaries); assertPackageDexUsage(
mPackageDexUsage,
/* usdeBy=*/ (Set<String>) null,
/* primaryDex= */ null,
expectedSecondaries);
} }
} }
@@ -276,13 +282,48 @@ public class PackageDexUsageTests {
Map<String, Set<String>> packageToCodePaths = new HashMap<>(); Map<String, Set<String>> packageToCodePaths = new HashMap<>();
packageToCodePaths.put(mBarBaseUser0.mPackageName, packageToCodePaths.put(mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile)));
mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths, new ArrayList<String>());
// Assert that only user 1 files are there. // Assert that only user 1 files are there.
assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1); assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1);
assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName)); assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
} }
@Test
public void testSyncDataKeepPackages() {
PackageDexUsage packageDexUsage = new PackageDexUsage();
// Write the record we want to keep and which won't be keep by default.
Set<String> fooUsers = new HashSet<>(Arrays.asList(
new String[] {mFooBaseUser0.mPackageName}));
assertTrue(record(packageDexUsage, mFooBaseUser0, fooUsers));
// Write a record that would be kept by default.
Set<String> barUsers = new HashSet<>(Arrays.asList(
new String[] {"another.package", mFooBaseUser0.mPackageName}));
assertTrue(record(packageDexUsage, mBarBaseUser0, barUsers));
// Construct the user packages and their code paths (things that will be
// kept by default during sync).
Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
packageToUsersMap.put(mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId)));
Map<String, Set<String>> packageToCodePaths = new HashMap<>();
packageToCodePaths.put(mBarBaseUser0.mPackageName,
new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile)));
// Sync data.
List<String> keepData = new ArrayList<String>();
keepData.add(mFooBaseUser0.mPackageName);
packageDexUsage.syncData(packageToUsersMap, packageToCodePaths, keepData);
// Assert that both packages are kept
assertPackageDexUsage(packageDexUsage, fooUsers, mFooBaseUser0);
// "another.package" should not be in the loading packages after sync.
Set<String> expectedBarUsers = new HashSet<>(Arrays.asList(
new String[] {mFooBaseUser0.mPackageName}));
assertPackageDexUsage(packageDexUsage, expectedBarUsers,
mBarBaseUser0.updateUsedBy(mFooBaseUser0.mPackageName));
}
@Test @Test
public void testRemovePackage() { public void testRemovePackage() {
// Record Bar secondaries for two different users. // Record Bar secondaries for two different users.
@@ -345,9 +386,8 @@ public class PackageDexUsageTests {
mFooSplit2UsedByOtherApps0.mDexFile, mFooSplit2UsedByOtherApps0.mDexFile,
mFooSplit2UsedByOtherApps0.mOwnerUserId, mFooSplit2UsedByOtherApps0.mOwnerUserId,
mFooSplit2UsedByOtherApps0.mLoaderIsa, mFooSplit2UsedByOtherApps0.mLoaderIsa,
/*mIsUsedByOtherApps*/false,
mFooSplit2UsedByOtherApps0.mPrimaryOrSplit, mFooSplit2UsedByOtherApps0.mPrimaryOrSplit,
mFooSplit2UsedByOtherApps0.mUsedBy); /*usedBy=*/ null);
assertPackageDexUsage(noLongerUsedByOtherApps); assertPackageDexUsage(noLongerUsedByOtherApps);
} }
@@ -371,19 +411,19 @@ public class PackageDexUsageTests {
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra));
assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); assertTrue(record(packageDexUsageRecordUsers, mFooSecondary2UsedByOtherApps0, usersExtra));
packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers);
// Verify that the users were recorded. // Verify that the users were recorded.
Set<String> userAll = new HashSet<>(users); Set<String> userAll = new HashSet<>(users);
userAll.addAll(usersExtra); userAll.addAll(usersExtra);
assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0, assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0,
mFooSecondary1User0); mFooSecondary2UsedByOtherApps0);
} }
@Test @Test
public void testRecordDexFileUsersNotTheOwningPackage() { public void testRecordDexFileUsersAndTheOwningPackage() {
PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage();
Set<String> users = new HashSet<>(Arrays.asList( Set<String> users = new HashSet<>(Arrays.asList(
new String[] {mFooSplit2UsedByOtherApps0.mPackageName})); new String[] {mFooSplit2UsedByOtherApps0.mPackageName}));
@@ -393,13 +433,13 @@ public class PackageDexUsageTests {
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra));
assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users));
assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra));
packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers);
// Verify that only the non owning packages were recorded.
assertPackageDexUsage(packageDexUsageRecordUsers, usersExtra, mFooSplit2UsedByOtherApps0, Set<String> expectedUsers = new HashSet<>(users);
mFooSecondary1User0); expectedUsers.addAll(usersExtra);
// Verify that all loading packages were recorded.
assertPackageDexUsage(
packageDexUsageRecordUsers, expectedUsers, mFooSplit2UsedByOtherApps0);
} }
@Test @Test
@@ -420,45 +460,98 @@ public class PackageDexUsageTests {
assertPackageDexUsage(null, expectedContext); assertPackageDexUsage(null, expectedContext);
} }
@Test
public void testRecordClassLoaderContextTransitionFromUnknown() {
// Record a secondary dex file.
TestData unknownContext = mFooSecondary1User0.updateClassLoaderContext(
PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT);
assertTrue(record(unknownContext));
assertPackageDexUsage(null, unknownContext);
writeAndReadBack();
assertPackageDexUsage(null, unknownContext);
// Now update the secondary dex record with a class loader context. This simulates the
// version 2 to version 3 upgrade.
assertTrue(record(mFooSecondary1User0));
assertPackageDexUsage(null, mFooSecondary1User0);
writeAndReadBack();
assertPackageDexUsage(null, mFooSecondary1User0);
}
@Test @Test
public void testDexUsageClassLoaderContext() { public void testDexUsageClassLoaderContext() {
final boolean isUsedByOtherApps = false; final boolean isUsedByOtherApps = false;
final int userId = 0; final int userId = 0;
PackageDexUsage.DexUseInfo validContext = new DexUseInfo(isUsedByOtherApps, userId, PackageDexUsage.DexUseInfo validContext = new DexUseInfo(isUsedByOtherApps, userId,
"valid_context", "arm"); "valid_context", "arm");
assertFalse(validContext.isUnknownClassLoaderContext()); assertFalse(validContext.isUnsupportedClassLoaderContext());
assertFalse(validContext.isVariableClassLoaderContext()); assertFalse(validContext.isVariableClassLoaderContext());
PackageDexUsage.DexUseInfo variableContext = new DexUseInfo(isUsedByOtherApps, userId, PackageDexUsage.DexUseInfo variableContext = new DexUseInfo(isUsedByOtherApps, userId,
PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, "arm"); PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, "arm");
assertFalse(variableContext.isUnknownClassLoaderContext()); assertFalse(variableContext.isUnsupportedClassLoaderContext());
assertTrue(variableContext.isVariableClassLoaderContext()); assertTrue(variableContext.isVariableClassLoaderContext());
}
PackageDexUsage.DexUseInfo unknownContext = new DexUseInfo(isUsedByOtherApps, userId, @Test
PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT, "arm"); public void testRead() {
assertTrue(unknownContext.isUnknownClassLoaderContext()); String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
assertFalse(unknownContext.isVariableClassLoaderContext()); // Equivalent to
// record(mFooSplit2UsedByOtherApps0);
// record(mFooSecondary1User0);
// record(mFooSecondary2UsedByOtherApps0);
// record(mBarBaseUser0);
// record(mBarSecondary1User0);
String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n"
+ "com.google.foo\n"
+ "+/data/app/com.google.foo/split-2.apk\n"
+ "@used.by.other.com\n"
+ "#/data/user/0/com.google.foo/sec-2.dex\n"
+ "0,1," + ISA + "\n"
+ "@used.by.other.com\n"
+ "PCL[/data/user/0/com.google.foo/sec-2.dex]\n"
+ "#/data/user/0/com.google.foo/sec-1.dex\n"
+ "0,0," + ISA + "\n"
+ "@\n"
+ "PCL[/data/user/0/com.google.foo/sec-1.dex]\n"
+ "com.google.bar\n"
+ "+/data/app/com.google.bar/base.apk\n"
+ "@com.google.bar\n"
+ "#/data/user/0/com.google.bar/sec-1.dex\n"
+ "0,0," + ISA + "\n"
+ "@\n"
+ "PCL[/data/user/0/com.google.bar/sec-1.dex]";
PackageDexUsage packageDexUsage = new PackageDexUsage();
try {
packageDexUsage.read(new StringReader(content));
} catch (IOException e) {
fail();
}
// After the read we must sync the data to fill the missing information on the code paths.
Map<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)));
// Handle the loading package.
packageToUsersMap.put(
mFooSplit2UsedByOtherApps0.mUsedBy,
new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId)));
// Sync the data.
packageDexUsage.syncData(packageToUsersMap, packageToCodePaths, new ArrayList<>());
// Assert foo code paths.
assertPackageDexUsage(
packageDexUsage,
/*nonDefaultUsers=*/ null,
mFooSplit2UsedByOtherApps0,
mFooSecondary2UsedByOtherApps0,
mFooSecondary1User0);
// Assert bar code paths.
assertPackageDexUsage(
packageDexUsage,
/*nonDefaultUsers=*/ null,
mBarBaseUser0,
mBarSecondary1User0);
} }
@Test @Test
@@ -483,77 +576,19 @@ public class PackageDexUsageTests {
} }
@Test @Test
public void testReadVersion1() { public void testEnsureLoadingPackagesCanBeExtended() {
String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
// Equivalent to String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__2\n"
// record(mFooSplit2UsedByOtherApps0); + "com.google.foo\n"
// record(mFooSecondary1User0); + "+/data/app/com.google.foo/split-2.apk\n"
// record(mFooSecondary2UsedByOtherApps0); + "@\n";
// 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(); PackageDexUsage packageDexUsage = new PackageDexUsage();
try { try {
packageDexUsage.read(new StringReader(content)); packageDexUsage.read(new StringReader(content));
} catch (IOException e) { } catch (IOException e) {
fail(); fail();
} }
record(packageDexUsage, mFooSplit2UsedByOtherApps0, mFooSplit2UsedByOtherApps0.getUsedBy());
// 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) { private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
@@ -570,16 +605,18 @@ public class PackageDexUsageTests {
String packageName = primary == null String packageName = primary == null
? secondaries.get(0).mPackageName ? secondaries.get(0).mPackageName
: primary.mPackageName; : primary.mPackageName;
boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps; boolean primaryUsedByOtherApps = primary != null && primary.isUsedByOtherApps();
PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName);
// Check package use info // Check package use info
assertNotNull(pInfo); assertNotNull(pInfo);
if (primary != null) { if (primary != null) {
assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile));
if (users != null) { if (users != null) {
assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users); assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users);
} else if (pInfo.getLoadingPackages(primary.mDexFile) != null) {
assertEquals(pInfo.getLoadingPackages(primary.mDexFile), primary.getUsedBy());
} }
assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile));
} }
Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap(); Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap();
@@ -589,13 +626,15 @@ public class PackageDexUsageTests {
for (TestData testData : secondaries) { for (TestData testData : secondaries) {
DexUseInfo dInfo = dexUseInfoMap.get(testData.mDexFile); DexUseInfo dInfo = dexUseInfoMap.get(testData.mDexFile);
assertNotNull(dInfo); assertNotNull(dInfo);
assertEquals(testData.mUsedByOtherApps, dInfo.isUsedByOtherApps()); if (users != null) {
assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), users);
} else {
assertEquals(testData.mDexFile, dInfo.getLoadingPackages(), testData.getUsedBy());
}
assertEquals(testData.isUsedByOtherApps(), dInfo.isUsedByOtherApps());
assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId()); assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId());
assertEquals(1, dInfo.getLoaderIsas().size()); assertEquals(1, dInfo.getLoaderIsas().size());
assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa)); assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa));
if (users != null) {
assertEquals(dInfo.getLoadingPackages(), users);
}
assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext()); assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext());
} }
@@ -603,7 +642,7 @@ public class PackageDexUsageTests {
private boolean record(TestData testData) { private boolean record(TestData testData) {
return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile,
testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, testData.mOwnerUserId, testData.mLoaderIsa,
testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext); testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext);
} }
@@ -611,7 +650,7 @@ public class PackageDexUsageTests {
boolean result = true; boolean result = true;
for (String user : users) { for (String user : users) {
result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile,
testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, testData.mOwnerUserId, testData.mLoaderIsa,
testData.mPrimaryOrSplit, user, testData.mClassLoaderContext); testData.mPrimaryOrSplit, user, testData.mClassLoaderContext);
} }
return result; return result;
@@ -640,37 +679,49 @@ public class PackageDexUsageTests {
private final String mDexFile; private final String mDexFile;
private final int mOwnerUserId; private final int mOwnerUserId;
private final String mLoaderIsa; private final String mLoaderIsa;
private final boolean mUsedByOtherApps;
private final boolean mPrimaryOrSplit; private final boolean mPrimaryOrSplit;
private final String mUsedBy; private final String mUsedBy;
private final String mClassLoaderContext; private final String mClassLoaderContext;
private TestData(String packageName, String dexFile, int ownerUserId, private TestData(String packageName, String dexFile, int ownerUserId,
String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { String loaderIsa, boolean primaryOrSplit, String usedBy) {
this(packageName, dexFile, ownerUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, this(packageName, dexFile, ownerUserId, loaderIsa, primaryOrSplit,
usedBy, "DefaultClassLoaderContextFor_" + dexFile); usedBy, "PCL[" + dexFile + "]");
} }
private TestData(String packageName, String dexFile, int ownerUserId, private TestData(String packageName, String dexFile, int ownerUserId,
String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy, String loaderIsa, boolean primaryOrSplit, String usedBy,
String classLoaderContext) { String classLoaderContext) {
mPackageName = packageName; mPackageName = packageName;
mDexFile = dexFile; mDexFile = dexFile;
mOwnerUserId = ownerUserId; mOwnerUserId = ownerUserId;
mLoaderIsa = loaderIsa; mLoaderIsa = loaderIsa;
mUsedByOtherApps = isUsedByOtherApps;
mPrimaryOrSplit = primaryOrSplit; mPrimaryOrSplit = primaryOrSplit;
mUsedBy = usedBy; mUsedBy = usedBy;
mClassLoaderContext = classLoaderContext; mClassLoaderContext = classLoaderContext;
} }
private TestData updateClassLoaderContext(String newContext) { private TestData updateClassLoaderContext(String newContext) {
return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mUsedByOtherApps, return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa,
mPrimaryOrSplit, mUsedBy, newContext); mPrimaryOrSplit, mUsedBy, newContext);
} }
private TestData updateUseByOthers(boolean newUsedByOthers) { private TestData updateUsedBy(String newUsedBy) {
return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, newUsedByOthers, return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa,
mPrimaryOrSplit, mUsedBy, mClassLoaderContext); mPrimaryOrSplit, newUsedBy, mClassLoaderContext);
}
private boolean isUsedByOtherApps() {
return mUsedBy != null && !mPackageName.equals(mUsedBy);
}
private Set<String> getUsedBy() {
Set<String> users = new HashSet<>();
if ((mUsedBy != null) && (mPrimaryOrSplit || isUsedByOtherApps())) {
// We do not store the loading package for secondary dex files
// which are not used by others.
users.add(mUsedBy);
}
return users;
} }
} }
} }