diff options
| author | 2017-11-01 22:27:29 +0000 | |
|---|---|---|
| committer | 2017-11-01 22:27:29 +0000 | |
| commit | a47c4440fd4daa1c67c19191e89c9c6762e00ccc (patch) | |
| tree | 578fee64a75e84ac804e5ffc2accaaf48ef9a87a | |
| parent | 71ae6b3aabcf0588069c79bedf4bf9d5dd2a7890 (diff) | |
| parent | fe177b78d78470f1dd3801f50486f7b2b3876b7f (diff) | |
Merge changes Ie8b78c7c,If02081d2
am: fe177b78d7
Change-Id: I2720d1e1433367e9f584451408951dce0b61e0da
10 files changed, 583 insertions, 81 deletions
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java index fc697a30e6ae..d15dd6d71f94 100644 --- a/core/java/android/app/DexLoadReporter.java +++ b/core/java/android/app/DexLoadReporter.java @@ -31,7 +31,7 @@ import libcore.io.Libcore; import java.io.File; import java.io.IOException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -116,13 +116,18 @@ import java.util.Set; registerSecondaryDexForProfiling(dexPathsForRegistration); } - private void notifyPackageManager(List<BaseDexClassLoader> ignored, + private void notifyPackageManager(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths) { + // Get the class loader names for the binder call. + List<String> classLoadersNames = new ArrayList<>(classPaths.size()); + for (BaseDexClassLoader bdc : classLoadersChain) { + classLoadersNames.add(bdc.getClass().getName()); + } String packageName = ActivityThread.currentPackageName(); try { // Notify only the paths of the first class loader for now. ActivityThread.getPackageManager().notifyDexLoad( - packageName, Arrays.asList(classPaths.get(0).split(File.pathSeparator)), + packageName, classLoadersNames, classPaths, VMRuntime.getRuntime().vmInstructionSet()); } catch (RemoteException re) { Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 64d687e9d3de..c9afd6b7e930 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -469,11 +469,19 @@ interface IPackageManager { * Notify the package manager that a list of dex files have been loaded. * * @param loadingPackageName the name of the package who performs the load - * @param dexPats the list of the dex files paths that have been loaded + * @param classLoadersNames the names of the class loaders present in the loading chain. The + * list encodes the class loader chain in the natural order. The first class loader has + * the second one as its parent and so on. The dex files present in the class path of the + * first class loader will be recorded in the usage file. + * @param classPaths the class paths corresponding to the class loaders names from + * {@param classLoadersNames}. The the first element corresponds to the first class loader + * and so on. A classpath is represented as a list of dex files separated by + * {@code File.pathSeparator}. + * The dex files found in the first class path will be recorded in the usage file. * @param loaderIsa the ISA of the loader process */ - oneway void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, - String loaderIsa); + oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames, + in List<String> classPaths, String loaderIsa); /** * Register an application dex module with the package manager. diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 68d9227ece43..817fc903c39d 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -34,12 +34,12 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.DexoptUtils; +import com.android.server.pm.dex.PackageDexUsage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Set; import dalvik.system.DexFile; @@ -263,13 +263,12 @@ public class PackageDexOptimizer { * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though * that seems wasteful. */ - public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas, - String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) { + public int dexOptSecondaryDexPath(ApplicationInfo info, String path, + PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { synchronized (mInstallLock) { final long acquireTime = acquireWakeLockLI(info.uid); try { - return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter, - isUsedByOtherApps, downgrade); + return dexOptSecondaryDexPathLI(info, path, dexUseInfo, options); } finally { releaseWakeLockLI(acquireTime); } @@ -310,9 +309,16 @@ public class PackageDexOptimizer { } @GuardedBy("mInstallLock") - private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas, - String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) { - compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps); + private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, + PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { + if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) { + // We are asked to optimize only the dex files used by other apps and this is not + // on of them: skip it. + return DEX_OPT_SKIPPED; + } + + String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(), + dexUseInfo.isUsedByOtherApps()); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. // Secondary dex files are currently not compiled at boot. int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true) @@ -329,20 +335,32 @@ public class PackageDexOptimizer { return DEX_OPT_FAILED; } Log.d(TAG, "Running dexopt on: " + path - + " pkg=" + info.packageName + " isa=" + isas + + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas() + " dexoptFlags=" + printDexoptFlags(dexoptFlags) + " target-filter=" + compilerFilter); + String classLoaderContext; + if (dexUseInfo.isUnknownClassLoaderContext() || + dexUseInfo.isUnsupportedClassLoaderContext() || + dexUseInfo.isVariableClassLoaderContext()) { + // If we have an unknown (not yet set), unsupported (custom class loaders), or a + // variable class loader chain, compile without a context and mark the oat file with + // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation. + // TODO(calin): We should just extract in this case. + classLoaderContext = SKIP_SHARED_LIBRARY_CHECK; + } else { + classLoaderContext = dexUseInfo.getClassLoaderContext(); + } try { - for (String isa : isas) { + for (String isa : dexUseInfo.getLoaderIsas()) { // Reuse the same dexopt path as for the primary apks. We don't need all the // arguments as some (dexopNeeded and oatDir) will be computed by installd because // system server cannot read untrusted app content. // TODO(calin): maybe add a separate call. mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, /*oatDir*/ null, dexoptFlags, - compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser, - downgrade); + compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser, + options.isDowngrade()); } return DEX_OPT_PERFORMED; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8f4e8de987e9..946f6cacd6af 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9461,7 +9461,8 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) { + public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames, + List<String> classPaths, String loaderIsa) { int userId = UserHandle.getCallingUserId(); ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); if (ai == null) { @@ -9469,7 +9470,7 @@ public class PackageManagerService extends IPackageManager.Stub + loadingPackageName + ", user=" + userId); return; } - mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId); + mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId); } @Override 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 b2851d7cbeab..947e01c49c59 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -97,29 +97,55 @@ public class DexManager { * return as fast as possible. * * @param loadingAppInfo the package performing the load - * @param dexPaths the list of dex files being loaded + * @param classLoadersNames the names of the class loaders present in the loading chain. The + * list encodes the class loader chain in the natural order. The first class loader has + * the second one as its parent and so on. The dex files present in the class path of the + * first class loader will be recorded in the usage file. + * @param classPaths the class paths corresponding to the class loaders names from + * {@param classLoadersNames}. The the first element corresponds to the first class loader + * and so on. A classpath is represented as a list of dex files separated by + * {@code File.pathSeparator}. + * The dex files found in the first class path will be recorded in the usage file. * @param loaderIsa the ISA of the app loading the dex files * @param loaderUserId the user id which runs the code loading the dex files */ - public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, - String loaderIsa, int loaderUserId) { + public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames, + List<String> classPaths, String loaderIsa, int loaderUserId) { try { - notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); + notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa, + loaderUserId); } catch (Exception e) { Slog.w(TAG, "Exception while notifying dex load for package " + loadingAppInfo.packageName, e); } } - private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, - String loaderIsa, int loaderUserId) { + private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, + List<String> classLoaderNames, List<String> classPaths, String loaderIsa, + int loaderUserId) { + if (classLoaderNames.size() != classPaths.size()) { + Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size"); + return; + } + if (classLoaderNames.isEmpty()) { + Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty"); + return; + } if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { - Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + + Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " + loaderIsa + "?"); return; } - for (String dexPath : dexPaths) { + // The classpath is represented as a list of dex files separated by File.pathSeparator. + String[] dexPathsToRegister = classPaths.get(0).split(File.pathSeparator); + + // Encode the class loader contexts for the dexPathsToRegister. + String[] classLoaderContexts = DexoptUtils.processContextForDexLoad( + classLoaderNames, classPaths); + + int dexPathIndex = 0; + for (String dexPath : dexPathsToRegister) { // Find the owning package name. DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); @@ -147,24 +173,25 @@ public class DexManager { // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, // or UsedBytOtherApps), record will return true and we trigger an async write // to disk to make sure we don't loose the data in case of a reboot. + + // A null classLoaderContexts means that there are unsupported class loaders in the + // chain. + String classLoaderContext = classLoaderContexts == null + ? PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT + : classLoaderContexts[dexPathIndex]; if (mPackageDexUsage.record(searchResult.mOwningPackageName, dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, - loadingAppInfo.packageName)) { + loadingAppInfo.packageName, classLoaderContext)) { mPackageDexUsage.maybeWriteAsync(); } } else { - // This can happen in a few situations: - // - bogus dex loads - // - recent installs/uninstalls that we didn't detect. - // - new installed splits // If we can't find the owner of the dex we simply do not track it. The impact is // that the dex file will not be considered for offline optimizations. - // TODO(calin): add hooks for move/uninstall notifications to - // capture package moves or obsolete packages. if (DEBUG) { Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); } } + dexPathIndex++; } } @@ -328,10 +355,8 @@ public class DexManager { for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { String dexPath = entry.getKey(); DexUseInfo dexUseInfo = entry.getValue(); - if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) { - continue; - } - PackageInfo pkg = null; + + PackageInfo pkg; try { pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, dexUseInfo.getOwnerUserId()); @@ -350,8 +375,7 @@ public class DexManager { } int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, - dexUseInfo.getLoaderIsas(), options.getCompilerFilter(), - dexUseInfo.isUsedByOtherApps(), options.isDowngrade()); + dexUseInfo, options); success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); } return success; @@ -434,6 +458,8 @@ public class DexManager { } } + // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the + // compilation happening here will use a pessimistic context. public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, boolean isUsedByOtherApps, int userId) { // Find the owning package record. @@ -452,12 +478,11 @@ public class DexManager { // We found the package. Now record the usage for all declared ISAs. boolean update = false; - Set<String> isas = new HashSet<>(); for (String isa : getAppDexInstructionSets(info)) { - isas.add(isa); boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, - searchResult.mOwningPackageName); + searchResult.mOwningPackageName, + PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); update |= newUpdate; } if (update) { @@ -467,8 +492,13 @@ public class DexManager { // Try to optimize the package according to the install reason. String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason( PackageManagerService.REASON_INSTALL); - int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas, - compilerFilter, isUsedByOtherApps, /* downgrade */ false); + DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName) + .getDexUseInfoMap().get(dexPath); + + DexoptOptions options = new DexoptOptions(info.packageName, compilerFilter, /*flags*/0); + + int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo, + options); // If we fail to optimize the package log an error but don't propagate the error // back to the app. The app cannot do much about it and the background job diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java index abac52f7a8da..bc8bf5e1fc4c 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java +++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java @@ -21,6 +21,8 @@ import android.util.Slog; import android.util.SparseArray; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public final class DexoptUtils { @@ -225,7 +227,81 @@ public final class DexoptUtils { * dependencies {@see encodeClassLoader} separated by ';'. */ private static String encodeClassLoaderChain(String cl1, String cl2) { - return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2); + if (cl1.isEmpty()) return cl2; + if (cl2.isEmpty()) return cl1; + return cl1 + ";" + cl2; + } + + /** + * Compute the class loader context for the dex files present in the classpath of the first + * class loader from the given list (referred in the code as the {@code loadingClassLoader}). + * Each dex files gets its own class loader context in the returned array. + * + * Example: + * If classLoadersNames = {"dalvik.system.DelegateLastClassLoader", + * "dalvik.system.PathClassLoader"} and classPaths = {"foo.dex:bar.dex", "other.dex"} + * The output will be + * {"DLC[];PCL[other.dex]", "DLC[foo.dex];PCL[other.dex]"} + * with "DLC[];PCL[other.dex]" being the context for "foo.dex" + * and "DLC[foo.dex];PCL[other.dex]" the context for "bar.dex". + * + * If any of the class loaders names is unsupported the method will return null. + * + * The argument lists must be non empty and of the same size. + * + * @param classLoadersNames the names of the class loaders present in the loading chain. The + * list encodes the class loader chain in the natural order. The first class loader has + * the second one as its parent and so on. + * @param classPaths the class paths for the elements of {@param classLoadersNames}. The + * the first element corresponds to the first class loader and so on. A classpath is + * represented as a list of dex files separated by {@code File.pathSeparator}. + * The return context will be for the dex files found in the first class path. + */ + /*package*/ static String[] processContextForDexLoad(List<String> classLoadersNames, + List<String> classPaths) { + if (classLoadersNames.size() != classPaths.size()) { + throw new IllegalArgumentException( + "The size of the class loader names and the dex paths do not match."); + } + if (classLoadersNames.isEmpty()) { + throw new IllegalArgumentException("Empty classLoadersNames"); + } + + // Compute the context for the parent class loaders. + String parentContext = ""; + // We know that these lists are actually ArrayLists so getting the elements by index + // is fine (they come over binder). Even if something changes we expect the sizes to be + // very small and it shouldn't matter much. + for (int i = 1; i < classLoadersNames.size(); i++) { + if (!isValidClassLoaderName(classLoadersNames.get(i))) { + return null; + } + String classpath = encodeClasspath(classPaths.get(i).split(File.pathSeparator)); + parentContext = encodeClassLoaderChain(parentContext, + encodeClassLoader(classpath, classLoadersNames.get(i))); + } + + // Now compute the class loader context for each dex file from the first classpath. + String loadingClassLoader = classLoadersNames.get(0); + if (!isValidClassLoaderName(loadingClassLoader)) { + return null; + } + String[] loadedDexPaths = classPaths.get(0).split(File.pathSeparator); + String[] loadedDexPathsContext = new String[loadedDexPaths.length]; + String currentLoadedDexPathClasspath = ""; + for (int i = 0; i < loadedDexPaths.length; i++) { + String dexPath = loadedDexPaths[i]; + String currentContext = encodeClassLoader( + currentLoadedDexPathClasspath, loadingClassLoader); + loadedDexPathsContext[i] = encodeClassLoaderChain(currentContext, parentContext); + currentLoadedDexPathClasspath = encodeClasspath(currentLoadedDexPathClasspath, dexPath); + } + return loadedDexPathsContext; + } + + // AOSP-only hack. + private static boolean isValidClassLoaderName(String name) { + return "dalvik.system.PathClassLoader".equals(name) || "dalvik.system.DexClassLoader".equals(name); } /** 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 be7f7320889f..6ee26d32f0e0 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -26,7 +26,6 @@ import com.android.server.pm.AbstractStatsBase; import com.android.server.pm.PackageManagerServiceUtils; import java.io.BufferedReader; -import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStreamReader; @@ -36,7 +35,6 @@ 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; @@ -55,10 +53,12 @@ import libcore.util.Objects; public class PackageDexUsage extends AbstractStatsBase<Void> { private final static String TAG = "PackageDexUsage"; - // The last version update: add the list of packages that load the dex files. - private final static int PACKAGE_DEX_USAGE_VERSION = 2; - // We support VERSION 1 to ensure that the usage list remains valid cross OTAs. + // 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. + private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2 = 2; private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; @@ -66,6 +66,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { private final static String SPLIT_CHAR = ","; private final static String DEX_LINE_CHAR = "#"; private final static String LOADING_PACKAGE_CHAR = "@"; + + // 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. + // Instead we put a special marker in the dex usage file in order to recognize the case and + // skip optimizations on that dex files. + /*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT = + "=VariableClassLoaderContext="; + // The marker used for unsupported class loader contexts. + /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = + "=UnsupportedClassLoaderContext="; + // 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="; + // Map which structures the information we have on a package. // Maps package name to package data (which stores info about UsedByOtherApps and // secondary dex files.). @@ -98,10 +113,14 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { */ public boolean record(String owningPackageName, String dexPath, int ownerUserId, String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, - String loadingPackageName) { + String loadingPackageName, String classLoaderContext) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); } + if (classLoaderContext == null) { + throw new IllegalArgumentException("Null classLoaderContext"); + } + synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); if (packageUseInfo == null) { @@ -117,7 +136,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } 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. - DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa); + DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, + classLoaderContext, loaderIsa); packageUseInfo.mDexUseInfoMap.put(dexPath, newData); maybeAddLoadingPackage(owningPackageName, loadingPackageName, newData.mLoadingPackages); @@ -134,7 +154,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { return packageUseInfo.merge(isUsedByOtherApps) || updateLoadingPackages; } else { DexUseInfo newData = new DexUseInfo( - isUsedByOtherApps, ownerUserId, loaderIsa); + isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, loadingPackageName, newData.mLoadingPackages); @@ -253,8 +273,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { 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(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))); @@ -314,8 +335,10 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { while ((s = in.readLine()) != null) { if (s.startsWith(DEX_LINE_CHAR)) { // This is the start of the the dex lines. - // We expect two lines for each dex entry: + // We expect 4 lines for each dex entry: // #dexPaths + // @loading_package_1,loading_package_2,... + // class_loader_context // onwerUserId,isUsedByOtherApps,isa1,isa2 if (currentPackage == null) { throw new IllegalStateException( @@ -327,11 +350,13 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // 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) { - throw new IllegalStateException("Could not find dexUseInfo for line: " + s); + throw new IllegalStateException("Could not find dexUseInfo line"); } // We expect at least 3 elements (isUsedByOtherApps, userId, isa). @@ -341,9 +366,9 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } int ownerUserId = Integer.parseInt(elems[0]); boolean isUsedByOtherApps = readBoolean(elems[1]); - DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId); + DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId, + classLoaderContext, /*isa*/ null); dexUseInfo.mLoadingPackages.addAll(loadingPackages); - for (int i = 2; i < elems.length; i++) { String isa = elems[i]; if (supportedIsas.contains(isa)) { @@ -383,12 +408,30 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } /** - * Reads the list of loading packages from the buffer {@parm in} if + * Reads the class loader context encoding from the buffer {@code in} if * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}. */ + private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { + String context = null; + if (version == PACKAGE_DEX_USAGE_VERSION) { + context = in.readLine(); + if (context == null) { + throw new IllegalStateException("Could not find the classLoaderContext line."); + } + } + // 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 + * {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}. + */ private List<String> maybeReadLoadingPackages(BufferedReader in, int version) throws IOException { - if (version == PACKAGE_DEX_USAGE_VERSION) { + if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { String line = in.readLine(); if (line == null) { throw new IllegalStateException("Could not find the loadingPackages line."); @@ -664,17 +707,20 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public static class DexUseInfo { private boolean mIsUsedByOtherApps; private final int mOwnerUserId; + // The class loader context for the dex file. This encodes the class loader chain + // (class loader type + class path) in a format compatible to dex2oat. + // See {@code DexoptUtils.processContextForDexLoad}. + private String mClassLoaderContext; + // The instructions sets of the applications loading the dex file. private final Set<String> mLoaderIsas; // Packages who load this dex file. private final Set<String> mLoadingPackages; - public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) { - this(isUsedByOtherApps, ownerUserId, null); - } - - public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) { + public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String classLoaderContext, + String loaderIsa) { mIsUsedByOtherApps = isUsedByOtherApps; mOwnerUserId = ownerUserId; + mClassLoaderContext = classLoaderContext; mLoaderIsas = new HashSet<>(); if (loaderIsa != null) { mLoaderIsas.add(loaderIsa); @@ -686,6 +732,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public DexUseInfo(DexUseInfo other) { mIsUsedByOtherApps = other.mIsUsedByOtherApps; mOwnerUserId = other.mOwnerUserId; + mClassLoaderContext = other.mClassLoaderContext; mLoaderIsas = new HashSet<>(other.mLoaderIsas); mLoadingPackages = new HashSet<>(other.mLoadingPackages); } @@ -695,8 +742,24 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); - return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps) || - updateLoadingPackages; + + String oldClassLoaderContext = mClassLoaderContext; + if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) { + // Can happen if we read a previous version. + mClassLoaderContext = dexUseInfo.mClassLoaderContext; + } else if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(dexUseInfo.mClassLoaderContext)) { + // We detected an unsupported context. + mClassLoaderContext = UNSUPPORTED_CLASS_LOADER_CONTEXT; + } else if (!UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext) && + !Objects.equal(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { + // We detected a context change. + mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; + } + + return updateIsas || + (oldIsUsedByOtherApps != mIsUsedByOtherApps) || + updateLoadingPackages + || !Objects.equal(oldClassLoaderContext, mClassLoaderContext); } public boolean isUsedByOtherApps() { @@ -714,5 +777,21 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public Set<String> getLoadingPackages() { return mLoadingPackages; } + + public String getClassLoaderContext() { return mClassLoaderContext; } + + public boolean isUnsupportedClassLoaderContext() { + return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); + } + + public boolean isUnknownClassLoaderContext() { + // The class loader context may be unknown if we loaded the data from a previous version + // which didn't save the context. + return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); + } + + public boolean isVariableClassLoaderContext() { + return VARIABLE_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); + } } } 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 afc0f67fe993..e2dfb29be561 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 @@ -23,10 +23,14 @@ import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,6 +52,10 @@ import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; @RunWith(AndroidJUnit4.class) @SmallTest public class DexManagerTests { + private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); + private static final String DELEGATE_LAST_CLASS_LOADER_NAME = + DelegateLastClassLoader.class.getName(); + private DexManager mDexManager; private TestData mFooUser0; @@ -56,6 +64,9 @@ public class DexManagerTests { private TestData mInvalidIsa; private TestData mDoesNotExist; + private TestData mBarUser0UnsupportedClassLoader; + private TestData mBarUser0DelegateLastClassLoader; + private int mUser0; private int mUser1; @@ -68,12 +79,17 @@ public class DexManagerTests { String foo = "foo"; String bar = "bar"; - mFooUser0 = new TestData(foo, isa, mUser0); - mBarUser0 = new TestData(bar, isa, mUser0); - mBarUser1 = new TestData(bar, isa, mUser1); + mFooUser0 = new TestData(foo, isa, mUser0, PATH_CLASS_LOADER_NAME); + mBarUser0 = new TestData(bar, isa, mUser0, PATH_CLASS_LOADER_NAME); + mBarUser1 = new TestData(bar, isa, mUser1, PATH_CLASS_LOADER_NAME); mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); + mBarUser0UnsupportedClassLoader = new TestData(bar, isa, mUser0, + "unsupported.class_loader"); + mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, + DELEGATE_LAST_CLASS_LOADER_NAME); + mDexManager = new DexManager(null, null, null, null); // Foo and Bar are available to user0. @@ -373,8 +389,82 @@ public class DexManagerTests { assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } + @Test + public void testNotifyUnsupportedClassLoader() { + List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); + notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + // We expect that all the contexts are unsupported. + String[] expectedContexts = + Collections.nCopies(secondaries.size(), + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT).toArray(new String[0]); + assertSecondaryUse(mBarUser0UnsupportedClassLoader, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0, expectedContexts); + } + + @Test + public void testNotifyVariableClassLoader() { + // Record bar secondaries with the default PathClassLoader. + List<String> secondaries = mBarUser0.getSecondaryDexPaths(); + + notifyDexLoad(mBarUser0, secondaries, mUser0); + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + + // Record bar secondaries again with a different class loader. This will change the context. + notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); + + pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + // We expect that all the contexts to be changed to variable now. + String[] expectedContexts = + Collections.nCopies(secondaries.size(), + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT).toArray(new String[0]); + assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0, + expectedContexts); + } + + @Test + public void testNotifyUnsupportedClassLoaderDoesNotChange() { + List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); + notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + // We expect that all the contexts are unsupported. + String[] expectedContexts = + Collections.nCopies(secondaries.size(), + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT).toArray(new String[0]); + assertSecondaryUse(mBarUser0UnsupportedClassLoader, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0, expectedContexts); + + // Record bar secondaries again with a different class loader. This will change the context. + // However, because the context was already marked as unsupported we should not chage it. + notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); + pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); + assertSecondaryUse(mBarUser0UnsupportedClassLoader, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0, expectedContexts); + + } + + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, - List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { + List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, + String[] expectedContexts) { + assertNotNull(expectedContexts); + assertEquals(expectedContexts.length, secondaries.size()); + int index = 0; for (String dex : secondaries) { DexUseInfo dui = pui.getDexUseInfoMap().get(dex); assertNotNull(dui); @@ -382,11 +472,29 @@ public class DexManagerTests { assertEquals(ownerUserId, dui.getOwnerUserId()); assertEquals(1, dui.getLoaderIsas().size()); assertTrue(dui.getLoaderIsas().contains(testData.mLoaderIsa)); + assertEquals(expectedContexts[index++], dui.getClassLoaderContext()); } } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, + List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { + String[] expectedContexts = DexoptUtils.processContextForDexLoad( + Arrays.asList(testData.mClassLoader), + Arrays.asList(String.join(File.pathSeparator, secondaries))); + assertSecondaryUse(testData, pui, secondaries, isUsedByOtherApps, ownerUserId, + expectedContexts); + } private void notifyDexLoad(TestData testData, List<String> dexPaths, int loaderUserId) { - mDexManager.notifyDexLoad(testData.mPackageInfo.applicationInfo, dexPaths, + // By default, assume a single class loader in the chain. + // This makes writing tests much easier. + List<String> classLoaders = Arrays.asList(testData.mClassLoader); + List<String> classPaths = Arrays.asList(String.join(File.pathSeparator, dexPaths)); + notifyDexLoad(testData, classLoaders, classPaths, loaderUserId); + } + + private void notifyDexLoad(TestData testData, List<String> classLoader, List<String> classPaths, + int loaderUserId) { + mDexManager.notifyDexLoad(testData.mPackageInfo.applicationInfo, classLoader, classPaths, testData.mLoaderIsa, loaderUserId); } @@ -416,10 +524,16 @@ public class DexManagerTests { private static class TestData { private final PackageInfo mPackageInfo; private final String mLoaderIsa; + private final String mClassLoader; - private TestData(String packageName, String loaderIsa, int userId) { + private TestData(String packageName, String loaderIsa, int userId, String classLoader) { mPackageInfo = getMockPackageInfo(packageName, userId); mLoaderIsa = loaderIsa; + mClassLoader = classLoader; + } + + private TestData(String packageName, String loaderIsa, int userId) { + this(packageName, loaderIsa, userId, PATH_CLASS_LOADER_NAME); } private String getPackageName() { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index 21b286e68adf..ff7bd723d23d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -17,6 +17,9 @@ package com.android.server.pm.dex; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import android.content.pm.ApplicationInfo; import android.support.test.filters.SmallTest; @@ -24,14 +27,21 @@ import android.support.test.runner.AndroidJUnit4; import android.util.SparseArray; import dalvik.system.DelegateLastClassLoader; +import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + @RunWith(AndroidJUnit4.class) @SmallTest public class DexoptUtilsTest { + private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName(); private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); private static final String DELEGATE_LAST_CLASS_LOADER_NAME = DelegateLastClassLoader.class.getName(); @@ -202,4 +212,68 @@ public class DexoptUtilsTest { assertEquals(1, contexts.length); assertEquals("PCL[]", contexts[0]); } + + @Test + public void testProcessContextForDexLoad() { + List<String> classLoaders = Arrays.asList( + DELEGATE_LAST_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME); + List<String> classPaths = Arrays.asList( + String.join(File.pathSeparator, "foo.dex", "bar.dex"), + String.join(File.pathSeparator, "parent1.dex"), + String.join(File.pathSeparator, "parent2.dex", "parent3.dex")); + String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); + assertNotNull(context); + assertEquals(2, context.length); + assertEquals("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); + assertEquals("DLC[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); + } + + @Test + public void testProcessContextForDexLoadSingleElement() { + List<String> classLoaders = Arrays.asList(PATH_CLASS_LOADER_NAME); + List<String> classPaths = Arrays.asList( + String.join(File.pathSeparator, "foo.dex", "bar.dex", "zoo.dex")); + String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); + assertNotNull(context); + assertEquals(3, context.length); + assertEquals("PCL[]", context[0]); + assertEquals("PCL[foo.dex]", context[1]); + assertEquals("PCL[foo.dex:bar.dex]", context[2]); + } + + @Test + public void testProcessContextForDexLoadUnsupported() { + List<String> classLoaders = Arrays.asList( + DELEGATE_LAST_CLASS_LOADER_NAME, + "unsupported.class.loader"); + List<String> classPaths = Arrays.asList( + String.join(File.pathSeparator, "foo.dex", "bar.dex"), + String.join(File.pathSeparator, "parent1.dex")); + String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); + assertNull(context); + } + + @Test + public void testProcessContextForDexLoadIllegalCallEmptyList() { + boolean gotException = false; + try { + DexoptUtils.processContextForDexLoad(Collections.emptyList(), Collections.emptyList()); + } catch (IllegalArgumentException ignore) { + gotException = true; + } + assertTrue(gotException); + } + + @Test + public void testProcessContextForDexLoadIllegalCallDifferentSize() { + boolean gotException = false; + try { + DexoptUtils.processContextForDexLoad(Collections.emptyList(), Arrays.asList("a")); + } catch (IllegalArgumentException ignore) { + gotException = true; + } + assertTrue(gotException); + } } 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 e1ef41e71c34..3fc12b473429 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 @@ -359,7 +359,7 @@ public class PackageDexUsageTests { public void testRecordDexFileUsersNotTheOwningPackage() { PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); Set<String> users = new HashSet<>(Arrays.asList( - new String[] {mFooBaseUser0.mPackageName,})); + new String[] {mFooBaseUser0.mPackageName})); Set<String> usersExtra = new HashSet<>(Arrays.asList( new String[] {"another.package.2", "another.package.3"})); @@ -375,6 +375,89 @@ public class PackageDexUsageTests { mFooSecondary1User0); } + @Test + public void testRecordClassLoaderContextVariableContext() { + // Record a secondary dex file. + assertTrue(record(mFooSecondary1User0)); + // Now update its context. + TestData fooSecondary1User0NewContext = mFooSecondary1User0.updateClassLoaderContext( + "PCL[new_context.dex]"); + assertTrue(record(fooSecondary1User0NewContext)); + + // Not check that the context was switch to variable. + TestData expectedContext = mFooSecondary1User0.updateClassLoaderContext( + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); + + assertPackageDexUsage(null, expectedContext); + writeAndReadBack(); + assertPackageDexUsage(null, expectedContext); + } + + @Test + public void testRecordClassLoaderContextUnsupportedContext() { + // Record a secondary dex file. + assertTrue(record(mFooSecondary1User0)); + // Now update its context. + TestData unsupportedContext = mFooSecondary1User0.updateClassLoaderContext( + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT); + assertTrue(record(unsupportedContext)); + + assertPackageDexUsage(null, unsupportedContext); + writeAndReadBack(); + assertPackageDexUsage(null, unsupportedContext); + } + + @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 + public void testDexUsageClassLoaderContext() { + final boolean isUsedByOtherApps = false; + final int userId = 0; + PackageDexUsage.DexUseInfo validContext = new DexUseInfo(isUsedByOtherApps, userId, + "valid_context", "arm"); + assertFalse(validContext.isUnknownClassLoaderContext()); + assertFalse(validContext.isUnsupportedClassLoaderContext()); + assertFalse(validContext.isVariableClassLoaderContext()); + + PackageDexUsage.DexUseInfo unsupportedContext = new DexUseInfo(isUsedByOtherApps, userId, + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT, "arm"); + assertFalse(unsupportedContext.isUnknownClassLoaderContext()); + assertTrue(unsupportedContext.isUnsupportedClassLoaderContext()); + assertFalse(unsupportedContext.isVariableClassLoaderContext()); + + PackageDexUsage.DexUseInfo variableContext = new DexUseInfo(isUsedByOtherApps, userId, + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, "arm"); + assertFalse(variableContext.isUnknownClassLoaderContext()); + assertFalse(variableContext.isUnsupportedClassLoaderContext()); + assertTrue(variableContext.isVariableClassLoaderContext()); + + PackageDexUsage.DexUseInfo unknownContext = new DexUseInfo(isUsedByOtherApps, userId, + PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT, "arm"); + assertTrue(unknownContext.isUnknownClassLoaderContext()); + assertFalse(unknownContext.isUnsupportedClassLoaderContext()); + assertFalse(unknownContext.isVariableClassLoaderContext()); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { assertPackageDexUsage(mPackageDexUsage, null, primary, secondaries); } @@ -382,7 +465,7 @@ public class PackageDexUsageTests { private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users, TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; - boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; + boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps; PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); // Check package use info @@ -406,13 +489,15 @@ public class PackageDexUsageTests { if (users != null) { assertEquals(dInfo.getLoadingPackages(), users); } + + assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext()); } } private boolean record(TestData testData) { return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, - testData.mPrimaryOrSplit, testData.mUsedBy); + testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext); } private boolean record(PackageDexUsage packageDexUsage, TestData testData, Set<String> users) { @@ -420,7 +505,7 @@ public class PackageDexUsageTests { for (String user : users) { result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, - testData.mPrimaryOrSplit, user); + testData.mPrimaryOrSplit, user, testData.mClassLoaderContext); } return result; } @@ -451,9 +536,16 @@ public class PackageDexUsageTests { private final boolean mUsedByOtherApps; private final boolean mPrimaryOrSplit; private final String mUsedBy; + private final String mClassLoaderContext; private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { + this(packageName, dexFile, ownerUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, + usedBy, "DefaultClassLoaderContextFor_" + dexFile); + } + private TestData(String packageName, String dexFile, int ownerUserId, + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy, + String classLoaderContext) { mPackageName = packageName; mDexFile = dexFile; mOwnerUserId = ownerUserId; @@ -461,7 +553,12 @@ public class PackageDexUsageTests { mUsedByOtherApps = isUsedByOtherApps; mPrimaryOrSplit = primaryOrSplit; mUsedBy = usedBy; + mClassLoaderContext = classLoaderContext; } + private TestData updateClassLoaderContext(String newContext) { + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mUsedByOtherApps, + mPrimaryOrSplit, mUsedBy, newContext); + } } } |