Merge "Register secondary dex files for JIT profiling"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9b64f0c..39b9d66 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5579,7 +5579,7 @@
         // Make sure we do this before calling onCreate so that we can capture the
         // complete application startup.
         if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
-            BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE);
+            BaseDexClassLoader.setReporter(DexLoadReporter.getInstance());
         }
 
         // Install the Network Security Config Provider. This must happen before the application
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 009c448..13f288a 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -16,13 +16,21 @@
 
 package android.app;
 
+import android.os.FileUtils;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+
 import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * A dex load reporter which will notify package manager of any dex file loaded
@@ -37,15 +45,60 @@
 /*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter {
     private static final String TAG = "DexLoadReporter";
 
-    /*package*/ static final DexLoadReporter INSTANCE = new DexLoadReporter();
+    private static final DexLoadReporter INSTANCE = new DexLoadReporter();
 
-    private DexLoadReporter() {}
+    private static final boolean DEBUG = false;
+
+    // We must guard the access to the list of data directories because
+    // we might have concurrent accesses. Apps might load dex files while
+    // new data dirs are registered (due to creation of LoadedApks via
+    // create createApplicationContext).
+    @GuardedBy("mDataDirs")
+    private final Set<String> mDataDirs;
+
+    private DexLoadReporter() {
+        mDataDirs = new HashSet<>();
+    }
+
+    /*package*/ static DexLoadReporter getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Register an application data directory with the reporter.
+     * The data directories are used to determine if a dex file is secondary dex or not.
+     * Note that this method may be called multiple times for the same app, registering
+     * different data directories. This may happen when apps share the same user id
+     * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user
+     * id, and app1 loads app2 apk, then both data directories will be registered.
+     */
+    /*package*/ void registerAppDataDir(String packageName, String dataDir) {
+        if (DEBUG) {
+            Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir);
+        }
+        // TODO(calin): A few code paths imply that the data dir
+        // might be null. Investigate when that can happen.
+        if (dataDir != null) {
+            synchronized (mDataDirs) {
+                mDataDirs.add(dataDir);
+            }
+        }
+    }
 
     @Override
     public void report(List<String> dexPaths) {
         if (dexPaths.isEmpty()) {
             return;
         }
+        // Notify the package manager about the dex loads unconditionally.
+        // The load might be for either a primary or secondary dex file.
+        notifyPackageManager(dexPaths);
+        // Check for secondary dex files and register them for profiling if
+        // possible.
+        registerSecondaryDexForProfiling(dexPaths);
+    }
+
+    private void notifyPackageManager(List<String> dexPaths) {
         String packageName = ActivityThread.currentPackageName();
         try {
             ActivityThread.getPackageManager().notifyDexLoad(
@@ -54,4 +107,62 @@
             Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
         }
     }
+
+    private void registerSecondaryDexForProfiling(List<String> dexPaths) {
+        if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+            return;
+        }
+        // Make a copy of the current data directories so that we don't keep the lock
+        // while registering for profiling. The registration will perform I/O to
+        // check for or create the profile.
+        String[] dataDirs;
+        synchronized (mDataDirs) {
+            dataDirs = mDataDirs.toArray(new String[0]);
+        }
+        for (String dexPath : dexPaths) {
+            registerSecondaryDexForProfiling(dexPath, dataDirs);
+        }
+    }
+
+    private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) {
+        if (!isSecondaryDexFile(dexPath, dataDirs)) {
+            // The dex path is not a secondary dex file. Nothing to do.
+            return;
+        }
+        File secondaryProfile = getSecondaryProfileFile(dexPath);
+        try {
+            // Create the profile if not already there.
+            // Returns true if the file was created, false if the file already exists.
+            // or throws exceptions in case of errors.
+            boolean created = secondaryProfile.createNewFile();
+            if (DEBUG && created) {
+                Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile);
+            }
+        } catch (IOException ex) {
+            Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile +
+                    ":" + ex.getMessage());
+            // Don't move forward with the registration if we failed to create the profile.
+            return;
+        }
+
+        VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath });
+    }
+
+    // A dex file is a secondary dex file if it is in any of the registered app
+    // data directories.
+    private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) {
+        for (String dataDir : dataDirs) {
+            if (FileUtils.contains(dataDir, dexPath)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Secondary dex profiles are stored next to the dex file and have the same
+    // name with '.prof' appended.
+    // NOTE: Keep in sync with installd.
+    private File getSecondaryProfileFile(String dexPath) {
+        return new File(dexPath + ".prof");
+    }
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8299c50..cf41e4e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -750,6 +750,11 @@
 
         VMRuntime.registerAppInfo(profileFile.getPath(),
                 codePaths.toArray(new String[codePaths.size()]));
+
+        // Register the app data directory with the reporter. It will
+        // help deciding whether or not a dex file is the primary apk or a
+        // secondary dex.
+        DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir);
     }
 
     /**