Record usage information per split

Increase the granularity of usage information to store data on each split
separately.

Now, splits get their own useByOtherApps flag and can be compiled
speed-profile when only the primary apk is loaded by other apps.

Bug: 64124380
Test: runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/*

Change-Id: Ibf9e7b9e67db9c6f0f45dc695bce8fbeb7be20ae
This commit is contained in:
Calin Juravle
2017-08-04 01:42:17 -07:00
parent 3b74c41776
commit 52a452cf68
7 changed files with 325 additions and 137 deletions

View File

@@ -166,7 +166,7 @@ public class PackageDexOptimizer {
}
final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
|| packageUseInfo.isUsedByOtherApps();
|| packageUseInfo.isUsedByOtherApps(path);
final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
options.getCompilerFilter(), isUsedByOtherApps);
final boolean profileUpdated = options.isCheckForProfileUpdates() &&

View File

@@ -10006,7 +10006,7 @@ public class PackageManagerService extends IPackageManager.Stub
public void shutdown() {
mPackageUsage.writeNow(mPackages);
mCompilerStats.writeNow();
mDexManager.savePackageDexUsageNow();
mDexManager.writePackageDexUsageNow();
}
@Override

View File

@@ -138,8 +138,9 @@ public class PackageManagerServiceUtils {
// Give priority to apps used by other apps.
DexManager dexManager = packageManagerService.getDexManager();
applyPackageFilter((pkg) ->
dexManager.getPackageUseInfoOrDefault(pkg.packageName).isUsedByOtherApps(), result,
remainingPkgs, sortTemp, packageManagerService);
dexManager.getPackageUseInfoOrDefault(pkg.packageName)
.isAnyCodePathUsedByOtherApps(),
result, remainingPkgs, sortTemp, packageManagerService);
// Filter out packages that aren't recently used, add all remaining apps.
// TODO: add a property to control this?
@@ -212,7 +213,7 @@ public class PackageManagerServiceUtils {
boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
- latestPackageUseTimeInMillis)
< thresholdTimeinMillis)
&& packageUseInfo.isUsedByOtherApps();
&& packageUseInfo.isAnyCodePathUsedByOtherApps();
return !isActiveInBackgroundAndUsedByOtherPackages;
}

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;
@@ -310,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()) {
@@ -319,17 +323,26 @@ public class DexManager {
// Cache the code locations.
cachePackageInfo(pi, userId);
// Cache a map from package name to the set of user ids who installed the package.
// Cache two maps:
// - from package name to the set of user ids who installed the package.
// - from package name to the set of code paths.
// We will use it to sync the data and remove obsolete entries from
// mPackageDexUsage.
Set<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);
}
/**
@@ -608,9 +621,9 @@ public class DexManager {
}
/**
* Saves the in-memory package dex usage to disk right away.
* Writes the in-memory package dex usage to disk right away.
*/
public void savePackageDexUsageNow() {
public void writePackageDexUsageNow() {
mPackageDexUsage.writeNow();
}

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

@@ -116,7 +116,7 @@ public class DexManagerTests {
// Bar is used by others now and should be in our records
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertTrue(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, true);
assertTrue(pui.getDexUseInfoMap().isEmpty());
}
@@ -127,7 +127,7 @@ public class DexManagerTests {
notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(mFooUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mFooUser0, pui, false);
assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
}
@@ -139,7 +139,7 @@ public class DexManagerTests {
notifyDexLoad(mFooUser0, barSecondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, false);
assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0);
}
@@ -162,7 +162,7 @@ public class DexManagerTests {
// Check bar usage. Should be used by other app (for primary and barSecondaries).
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertTrue(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, true);
assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(),
pui.getDexUseInfoMap().size());
@@ -172,7 +172,7 @@ public class DexManagerTests {
// Check foo usage. Should not be used by other app.
pui = getPackageUseInfo(mFooUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mFooUser0, pui, false);
assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
}
@@ -191,7 +191,7 @@ public class DexManagerTests {
}
@Test
public void testNotExistingPackate() {
public void testNotExistingPackage() {
// Notifying about the load of a package which was previously not
// register in DexManager#load should be ignored.
notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0);
@@ -232,7 +232,7 @@ public class DexManagerTests {
// We should get back the right info.
PackageUseInfo pui = getPackageUseInfo(newPackage);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(newPackage, pui, false);
assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
}
@@ -248,7 +248,7 @@ public class DexManagerTests {
notifyDexLoad(newPackage, newSecondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(newPackage);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(newPackage, pui, false);
assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
}
@@ -260,7 +260,7 @@ public class DexManagerTests {
// Bar is used by others now and should be in our records.
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertTrue(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, true);
assertTrue(pui.getDexUseInfoMap().isEmpty());
// Notify that bar is updated.
@@ -270,7 +270,7 @@ public class DexManagerTests {
// The usedByOtherApps flag should be clear now.
pui = getPackageUseInfo(mBarUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, false);
}
@Test
@@ -293,7 +293,7 @@ public class DexManagerTests {
notifyDexLoad(mFooUser0, newSplits, mUser0);
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertNotNull(pui);
assertTrue(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(newSplits, pui, true);
}
@Test
@@ -325,7 +325,7 @@ public class DexManagerTests {
// Foo should still be around since it's used by other apps but with no
// secondary dex info.
PackageUseInfo pui = getPackageUseInfo(mFooUser0);
assertTrue(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mFooUser0, pui, true);
assertTrue(pui.getDexUseInfoMap().isEmpty());
}
@@ -370,7 +370,7 @@ public class DexManagerTests {
notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(mFooUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mFooUser0, pui, false);
assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);
}
@@ -381,7 +381,7 @@ public class DexManagerTests {
notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false);
assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
// We expect that all the contexts are unsupported.
String[] expectedContexts =
@@ -398,7 +398,7 @@ public class DexManagerTests {
notifyDexLoad(mBarUser0, secondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, false);
assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
@@ -406,7 +406,7 @@ public class DexManagerTests {
notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0);
pui = getPackageUseInfo(mBarUser0);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0, pui, false);
assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
// We expect that all the contexts to be changed to variable now.
String[] expectedContexts =
@@ -422,7 +422,7 @@ public class DexManagerTests {
notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader);
assertFalse(pui.isUsedByOtherApps());
assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false);
assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
// We expect that all the contexts are unsupported.
String[] expectedContexts =
@@ -466,6 +466,17 @@ public class DexManagerTests {
expectedContexts);
}
private void assertIsUsedByOtherApps(TestData testData, PackageUseInfo pui,
boolean isUsedByOtherApps) {
assertIsUsedByOtherApps(testData.getBaseAndSplitDexPaths(), pui, isUsedByOtherApps);
}
private void assertIsUsedByOtherApps(List<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.

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