Use PackageUseInfo in DexOptimizer

Pass the PackageUseInfo directly to DexOptimizer and use it to detect if a
package is used by other apps. Move the usage checks closer to dexopt so
that they can be easily adapted when we add usage info for each of the
app's code paths separately.

This is a refactoring CLs to reduce the size and complexity of the
upcoming CLs which record the usage info for each of the application
splits.

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

Change-Id: I8031590cdaff81ab1792ca19baddb6cb36dc021d
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 241d76f..da6e26e 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -318,7 +318,7 @@
         optimizer.performDexOpt(pkg, libraryDependencies,
                 null /* ISAs */,
                 null /* CompilerStats.PackageStats */,
-                mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName),
+                mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(pkg.packageName),
                 new DexoptOptions(pkg.packageName, compilationReason,
                         DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index dabd35c..0db1f01 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -31,6 +31,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.DexoptUtils;
 import com.android.server.pm.dex.PackageDexUsage;
@@ -112,7 +113,7 @@
      */
     int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
             String[] instructionSets, CompilerStats.PackageStats packageStats,
-            boolean isUsedByOtherApps, DexoptOptions options) {
+            PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
         if (!canOptimizePackage(pkg)) {
             return DEX_OPT_SKIPPED;
         }
@@ -120,7 +121,7 @@
             final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid);
             try {
                 return performDexOptLI(pkg, sharedLibraries, instructionSets,
-                        packageStats, isUsedByOtherApps, options);
+                        packageStats, packageUseInfo, options);
             } finally {
                 releaseWakeLockLI(acquireTime);
             }
@@ -134,21 +135,13 @@
     @GuardedBy("mInstallLock")
     private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
             String[] targetInstructionSets, CompilerStats.PackageStats packageStats,
-            boolean isUsedByOtherApps, DexoptOptions options) {
+            PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         final List<String> paths = pkg.getAllCodePaths();
         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
 
-        final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
-                options.getCompilerFilter(), isUsedByOtherApps);
-        final boolean profileUpdated = options.isCheckForProfileUpdates() &&
-                isProfileUpdated(pkg, sharedGid, compilerFilter);
-
-        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
-        final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
-
         // Get the class loader context dependencies.
         // For each code path in the package, this array contains the class loader context that
         // needs to be passed to dexopt in order to ensure correct optimizations.
@@ -172,6 +165,17 @@
                 }
             }
 
+            final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
+                    || packageUseInfo.isUsedByOtherApps();
+            final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
+                options.getCompilerFilter(), isUsedByOtherApps);
+            final boolean profileUpdated = options.isCheckForProfileUpdates() &&
+                isProfileUpdated(pkg, sharedGid, compilerFilter);
+
+            // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
+            // flags.
+            final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+
             for (String dexCodeIsa : dexCodeInstructionSets) {
                 int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
                         profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index aead98b..f0b3189 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9868,17 +9868,19 @@
         Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p);
         final String[] instructionSets = getAppDexInstructionSets(p.applicationInfo);
         if (!deps.isEmpty()) {
+            DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(),
+                    options.getCompilerFilter(), options.getSplitName(),
+                    options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY);
             for (PackageParser.Package depPackage : deps) {
                 // TODO: Analyze and investigate if we (should) profile libraries.
                 pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
                         getOrCreateCompilerPackageStats(depPackage),
-                        true /* isUsedByOtherApps */,
-                        options);
+                    mDexManager.getPackageUseInfoOrDefault(depPackage.packageName), libraryOptions);
             }
         }
         return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets,
                 getOrCreateCompilerPackageStats(p),
-                mDexManager.isUsedByOtherApps(p.packageName), options);
+                mDexManager.getPackageUseInfoOrDefault(p.packageName), options);
     }
 
     /**
@@ -18553,7 +18555,7 @@
                 mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
                         null /* instructionSets */,
                         getOrCreateCompilerPackageStats(pkg),
-                        mDexManager.isUsedByOtherApps(pkg.packageName),
+                        mDexManager.getPackageUseInfoOrDefault(pkg.packageName),
                         dexoptOptions);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
@@ -25376,8 +25378,8 @@
                 if (ps == null) {
                     continue;
                 }
-                PackageDexUsage.PackageUseInfo packageUseInfo = getDexManager().getPackageUseInfo(
-                        pkg.packageName);
+                PackageDexUsage.PackageUseInfo packageUseInfo =
+                      getDexManager().getPackageUseInfoOrDefault(pkg.packageName);
                 if (PackageManagerServiceUtils
                         .isUnusedSinceTimeInMillis(ps.firstInstallTime, currentTimeInMillis,
                                 downgradeTimeThresholdMillis, packageUseInfo,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index a7031c9..8e7e788 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -25,7 +25,6 @@
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
@@ -137,8 +136,9 @@
                 sortTemp, packageManagerService);
 
         // Give priority to apps used by other apps.
+        DexManager dexManager = packageManagerService.getDexManager();
         applyPackageFilter((pkg) ->
-                packageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), result,
+                dexManager.getPackageUseInfoOrDefault(pkg.packageName).isUsedByOtherApps(), result,
                 remainingPkgs, sortTemp, packageManagerService);
 
         // Filter out packages that aren't recently used, add all remaining apps.
@@ -209,12 +209,10 @@
 
         // If the app was active in background during the threshold period and was used
         // by other packages.
-        // If packageUseInfo is null, it can be said that the package was not used by other
-        // packages.
         boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
                 - latestPackageUseTimeInMillis)
                 < thresholdTimeinMillis)
-                && (packageUseInfo != null && packageUseInfo.isUsedByOtherApps());
+                && packageUseInfo.isUsedByOtherApps();
 
         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 947e01c4..352344b 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -81,6 +81,19 @@
     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
 
+    /**
+     * We do not record packages that have no secondary dex files or that are not used by other
+     * apps. This is an optimization to reduce the amount of data that needs to be written to
+     * disk (apps will not usually be shared so this trims quite a bit the number we record).
+     *
+     * To make this behaviour transparent to the callers which need use information on packages,
+     * DexManager will return this DEFAULT instance from
+     * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and
+     * is marked as not being used by other apps. This reflects the intended behaviour when we don't
+     * find the package in the underlying data file.
+     */
+    private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
+
     public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
             Installer installer, Object installLock) {
       mPackageCodeLocationsCache = new HashMap<>();
@@ -321,10 +334,29 @@
 
     /**
      * Get the package dex usage for the given package name.
-     * @return the package data or null if there is no data available for this package.
+     * If there is no usage info the method will return a default {@code PackageUseInfo} with
+     * no data about secondary dex files and marked as not being used by other apps.
+     *
+     * Note that no use info means the package was not used or it was used but not by other apps.
+     * Also, note that right now we might prune packages which are not used by other apps.
+     * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
+     * to access the package use.
      */
-    public PackageUseInfo getPackageUseInfo(String packageName) {
-        return mPackageDexUsage.getPackageUseInfo(packageName);
+    public PackageUseInfo getPackageUseInfoOrDefault(String packageName) {
+        PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName);
+        return useInfo == null ? DEFAULT_USE_INFO : useInfo;
+    }
+
+    /**
+     * Return whether or not the manager has usage information on the give package.
+     *
+     * Note that no use info means the package was not used or it was used but not by other apps.
+     * Also, note that right now we might prune packages which are not used by other apps.
+     * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
+     * to access the package use.
+     */
+    /*package*/ boolean hasInfoOnPackage(String packageName) {
+        return mPackageDexUsage.getPackageUseInfo(packageName) != null;
     }
 
     /**
@@ -343,7 +375,7 @@
                 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
                 : mPackageDexOptimizer;
         String packageName = options.getPackageName();
-        PackageUseInfo useInfo = getPackageUseInfo(packageName);
+        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
         if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
@@ -387,7 +419,7 @@
      * deleted, update the internal records and delete any generated oat files.
      */
     public void reconcileSecondaryDexFiles(String packageName) {
-        PackageUseInfo useInfo = getPackageUseInfo(packageName);
+        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
         if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
@@ -519,23 +551,6 @@
     }
 
     /**
-     * Return true if the profiling data collected for the given app indicate
-     * that the apps's APK has been loaded by another app.
-     * Note that this returns false for all apps without any collected profiling data.
-    */
-    public boolean isUsedByOtherApps(String packageName) {
-        PackageUseInfo useInfo = getPackageUseInfo(packageName);
-        if (useInfo == null) {
-            // No use info, means the package was not used or it was used but not by other apps.
-            // Note that right now we might prune packages which are not used by other apps.
-            // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
-            // to access the package use.
-            return false;
-        }
-        return useInfo.isUsedByOtherApps();
-    }
-
-    /**
      * Retrieves the package which owns the given dexPath.
      */
     private DexSearchResult getDexPackage(
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index f57cf5e..4fa47b5 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -50,6 +50,12 @@
     // save disk space.
     public static final int DEXOPT_DOWNGRADE = 1 << 5;
 
+    // When set, dexopt will compile the dex file as a shared library even if it is not actually
+    // used by other apps. This is used to force the compilation or shared libraries declared
+    // with in the manifest with ''uses-library' before we have a chance to detect they are
+    // actually shared at runtime.
+    public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6;
+
     // The name of package to optimize.
     private final String mPackageName;
 
@@ -79,7 +85,8 @@
                 DEXOPT_BOOT_COMPLETE |
                 DEXOPT_ONLY_SECONDARY_DEX |
                 DEXOPT_ONLY_SHARED_DEX |
-                DEXOPT_DOWNGRADE;
+                DEXOPT_DOWNGRADE |
+                DEXOPT_AS_SHARED_LIBRARY;
         if ((flags & (~validityMask)) != 0) {
             throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
         }
@@ -122,7 +129,15 @@
         return (mFlags & DEXOPT_DOWNGRADE) != 0;
     }
 
+    public boolean isDexoptAsSharedLibrary() {
+        return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0;
+    }
+
     public String getSplitName() {
         return mSplitName;
     }
+
+    public int getFlags() {
+        return mFlags;
+    }
 }