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.
(cherry picked from commit 52a452cf685c56dc6872dbb19e822736484f672f)
Bug: 64124380
Test: runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/*
Merged-In: Ibf9e7b9e67db9c6f0f45dc695bce8fbeb7be20ae
Change-Id: Ibf9e7b9e67db9c6f0f45dc695bce8fbeb7be20ae
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index a23ddd5..fadc32a 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -177,7 +177,7 @@
}
final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
- || packageUseInfo.isUsedByOtherApps();
+ || packageUseInfo.isUsedByOtherApps(path);
final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
options.getCompilerFilter(), isUsedByOtherApps);
final boolean profileUpdated = options.isCheckForProfileUpdates() &&
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2e0f62c..317c73f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9758,7 +9758,7 @@
public void shutdown() {
mPackageUsage.writeNow(mPackages);
mCompilerStats.writeNow();
- mDexManager.savePackageDexUsageNow();
+ mDexManager.writePackageDexUsageNow();
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 5ef9708..1e0ce7a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -145,8 +145,9 @@
// 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?
@@ -219,7 +220,7 @@
boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
- latestPackageUseTimeInMillis)
< thresholdTimeinMillis)
- && packageUseInfo.isUsedByOtherApps();
+ && packageUseInfo.isAnyCodePathUsedByOtherApps();
return !isActiveInBackgroundAndUsedByOtherPackages;
}
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 352344b..6274754 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -36,6 +36,8 @@
import 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 @@
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 @@
// 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 @@
}
/**
- * Saves the in-memory package dex usage to disk right away.
+ * Writes the in-memory package dex usage to disk right away.
*/
- public void savePackageDexUsageNow() {
+ public void writePackageDexUsageNow() {
mPackageDexUsage.writeNow();
}
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 6ee26d3..a4a0a54 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -35,6 +35,7 @@
import java.io.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 @@
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 @@
// 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 @@
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 @@
*
* 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 @@
// 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 @@
}
}
- String s;
+ String line;
String currentPackage = null;
PackageUseInfo currentPackageData = null;
@@ -332,8 +335,8 @@
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 @@
"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 @@
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 @@
*/
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 @@
* 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 @@
}
// 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 @@
}
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 @@
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 @@
* 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 @@
* @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 @@
}
// 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 @@
* @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 @@
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 @@
/**
* 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 @@
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 @@
* Stores data on how a package and its dex files are used.
*/
public static class PackageUseInfo {
- // This flag is for the primary and split apks. It is set to true whenever one of them
- // is loaded by another app.
- private boolean mIsUsedByOtherApps;
+ // The app's code paths that are used by other apps.
+ // The key is the code path and the value is the set of loading packages.
+ private final Map<String, Set<String>> mCodePathsUsedByOtherApps;
// Map dex paths to their data (isUsedByOtherApps, owner id, loader isa).
private final Map<String, DexUseInfo> mDexUseInfoMap;
- // Packages who load this dex file.
- private final Set<String> mLoadingPackages;
+
+ // Keeps track of whether or not this package was used by other apps before
+ // we upgraded to VERSION 4 which records the info for each code path separately.
+ // This is unwanted complexity but without it we risk to profile guide compile
+ // something that supposed to be shared. For example:
+ // 1) we determine that chrome is used by another app
+ // 2) we take an OTA which upgrades the way we keep track of usage data
+ // 3) chrome doesn't get used until the background job executes
+ // 4) as part of the backgound job we now think that chrome is not used by others
+ // and we speed-profile.
+ // 5) as a result the next time someone uses chrome it will extract from apk since
+ // the compiled code will be private.
+ private boolean mUsedByOtherAppsBeforeUpgrade;
public PackageUseInfo() {
- mIsUsedByOtherApps = false;
+ mCodePathsUsedByOtherApps = new HashMap<>();
mDexUseInfoMap = new HashMap<>();
- mLoadingPackages = new HashSet<>();
}
// Creates a deep copy of the `other`.
public PackageUseInfo(PackageUseInfo other) {
- mIsUsedByOtherApps = other.mIsUsedByOtherApps;
+ mCodePathsUsedByOtherApps = new HashMap<>();
+ for (Map.Entry<String, Set<String>> e : other.mCodePathsUsedByOtherApps.entrySet()) {
+ mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue()));
+ }
+
mDexUseInfoMap = new HashMap<>();
for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) {
mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue()));
}
- mLoadingPackages = new HashSet<>(other.mLoadingPackages);
}
- private boolean merge(boolean isUsedByOtherApps) {
- boolean oldIsUsedByOtherApps = mIsUsedByOtherApps;
- mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps;
- return oldIsUsedByOtherApps != this.mIsUsedByOtherApps;
+ private boolean mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps,
+ String owningPackageName, String loadingPackage) {
+ if (!isUsedByOtherApps) {
+ // Nothing to update if the the code path is not used by other apps.
+ return false;
+ }
+
+ boolean newCodePath = false;
+ Set<String> loadingPackages = mCodePathsUsedByOtherApps.get(codePath);
+ if (loadingPackages == null) {
+ loadingPackages = new HashSet<>();
+ mCodePathsUsedByOtherApps.put(codePath, loadingPackages);
+ newCodePath = true;
+ }
+ boolean newLoadingPackage = loadingPackage != null
+ && !loadingPackage.equals(owningPackageName)
+ && loadingPackages.add(loadingPackage);
+ return newCodePath || newLoadingPackage;
}
- public boolean isUsedByOtherApps() {
- return mIsUsedByOtherApps;
+ public boolean isUsedByOtherApps(String codePath) {
+ return mCodePathsUsedByOtherApps.containsKey(codePath);
}
public Map<String, DexUseInfo> getDexUseInfoMap() {
return mDexUseInfoMap;
}
- public Set<String> getLoadingPackages() {
- return mLoadingPackages;
+ public Set<String> getLoadingPackages(String codePath) {
+ return mCodePathsUsedByOtherApps.getOrDefault(codePath, null);
+ }
+
+ public boolean isAnyCodePathUsedByOtherApps() {
+ return !mCodePathsUsedByOtherApps.isEmpty();
+ }
+
+ /**
+ * Clears the usedByOtherApps markers from all code paths.
+ * Returns whether or not there was an update.
+ */
+ /*package*/ boolean clearCodePathUsedByOtherApps() {
+ // Update mUsedByOtherAppsBeforeUpgrade as well to be consistent with
+ // the new data. This is not saved to disk so we don't need to return it.
+ mUsedByOtherAppsBeforeUpgrade = true;
+
+ if (mCodePathsUsedByOtherApps.isEmpty()) {
+ return false;
+ } else {
+ mCodePathsUsedByOtherApps.clear();
+ return true;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 90c4f44..4db9a30 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -116,7 +116,7 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
}
@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 @@
// 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 @@
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 @@
// 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 @@
// The usedByOtherApps flag should be clear now.
pui = getPackageUseInfo(mBarUser0);
- assertFalse(pui.isUsedByOtherApps());
+ assertIsUsedByOtherApps(mBarUser0, pui, false);
}
@Test
@@ -293,7 +293,7 @@
notifyDexLoad(mFooUser0, newSplits, mUser0);
PackageUseInfo pui = getPackageUseInfo(mBarUser0);
assertNotNull(pui);
- assertTrue(pui.isUsedByOtherApps());
+ assertIsUsedByOtherApps(newSplits, pui, true);
}
@Test
@@ -325,7 +325,7 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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.
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
index 3fc12b4..69a148d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
@@ -21,6 +21,7 @@
import android.support.test.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 @@
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 @@
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 @@
// 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 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 @@
assertPackageDexUsage(null, mFooSecondary1User0);
}
-
@Test
public void testDexUsageClassLoaderContext() {
final boolean isUsedByOtherApps = false;
@@ -458,6 +461,80 @@
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 @@
// 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 @@
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);
+ }
}
}