Merge changes from topic "cp_calin_framework_2017"
am: b98102b4dc

Change-Id: I524b166fbd551200a539a40e3531883b494f68db
diff --git a/Android.bp b/Android.bp
index 985d734..010f496 100644
--- a/Android.bp
+++ b/Android.bp
@@ -133,6 +133,8 @@
         "core/java/android/content/pm/IPackageStatsObserver.aidl",
         "core/java/android/content/pm/IPinItemRequest.aidl",
         "core/java/android/content/pm/IShortcutService.aidl",
+        "core/java/android/content/pm/dex/IArtManager.aidl",
+        "core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl",
         "core/java/android/content/pm/permission/IRuntimePermissionPresenter.aidl",
         "core/java/android/database/IContentObserver.aidl",
         ":libcamera_client_aidl",
diff --git a/api/system-current.txt b/api/system-current.txt
index 56b5923..d2c6bd0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -126,6 +126,7 @@
     field public static final java.lang.String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
     field public static final java.lang.String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission.READ_PRINT_SERVICE_RECOMMENDATIONS";
     field public static final java.lang.String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
+    field public static final java.lang.String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
     field public static final java.lang.String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
     field public static final java.lang.String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
     field public static final java.lang.String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -847,6 +848,7 @@
   public abstract class PackageManager {
     method public abstract void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+    method public android.content.pm.dex.ArtManager getArtManager();
     method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
     method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
     method public abstract android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
@@ -948,6 +950,26 @@
 
 }
 
+package android.content.pm.dex {
+
+  public class ArtManager {
+    method public boolean isRuntimeProfilingEnabled(int);
+    method public void snapshotRuntimeProfile(int, java.lang.String, java.lang.String, java.util.concurrent.Executor, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback);
+    field public static final int PROFILE_APPS = 0; // 0x0
+    field public static final int PROFILE_BOOT_IMAGE = 1; // 0x1
+    field public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1; // 0x1
+    field public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2; // 0x2
+    field public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0; // 0x0
+  }
+
+  public static abstract class ArtManager.SnapshotRuntimeProfileCallback {
+    ctor public ArtManager.SnapshotRuntimeProfileCallback();
+    method public abstract void onError(int);
+    method public abstract void onSuccess(android.os.ParcelFileDescriptor);
+  }
+
+}
+
 package android.content.pm.permission {
 
   public final class RuntimePermissionPresentationInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 4d47d82..5bc1e76 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -56,6 +56,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.ArtManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
@@ -122,6 +123,8 @@
     private UserManager mUserManager;
     @GuardedBy("mLock")
     private PackageInstaller mInstaller;
+    @GuardedBy("mLock")
+    private ArtManager mArtManager;
 
     @GuardedBy("mDelegates")
     private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@@ -2763,4 +2766,18 @@
             throw e.rethrowAsRuntimeException();
         }
     }
+
+    @Override
+    public ArtManager getArtManager() {
+        synchronized (mLock) {
+            if (mArtManager == null) {
+                try {
+                    mArtManager = new ArtManager(mPM.getArtManager());
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return mArtManager;
+        }
+    }
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 236a42c..903031e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -28,6 +28,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.dex.ArtManager;
 import android.content.pm.split.SplitDependencyLoader;
 import android.content.res.AssetManager;
 import android.content.res.CompatibilityInfo;
@@ -35,7 +36,6 @@
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
@@ -49,13 +49,15 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
-import android.util.LogPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+
 import com.android.internal.util.ArrayUtils;
+
 import dalvik.system.VMRuntime;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -729,13 +731,6 @@
         }
     }
 
-    // Keep in sync with installd (frameworks/native/cmds/installd/commands.cpp).
-    private static File getPrimaryProfileFile(String packageName) {
-        File profileDir = Environment.getDataProfilesDePackageDirectory(
-                UserHandle.myUserId(), packageName);
-        return new File(profileDir, "primary.prof");
-    }
-
     private void setupJitProfileSupport() {
         if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
             return;
@@ -763,10 +758,12 @@
             return;
         }
 
-        final File profileFile = getPrimaryProfileFile(mPackageName);
-
-        VMRuntime.registerAppInfo(profileFile.getPath(),
-                codePaths.toArray(new String[codePaths.size()]));
+        for (int i = codePaths.size() - 1; i >= 0; i--) {
+            String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1];
+            String profileFile = ArtManager.getCurrentProfilePath(
+                    mPackageName, UserHandle.myUserId(), splitName);
+            VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)});
+        }
 
         // Register the app data directory with the reporter. It will
         // help deciding whether or not a dex file is the primary apk or a
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 0e70645..73a403d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -48,6 +48,7 @@
 import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.IArtManager;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
@@ -664,4 +665,6 @@
     ComponentName getInstantAppInstallerComponent();
 
     String getInstantAppAndroidId(String packageName, int userId);
+
+    IArtManager getArtManager();
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e7bf974..6f98adc 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -42,6 +42,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.dex.ArtManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
@@ -1316,6 +1317,15 @@
      */
     public static final int INSTALL_FAILED_INSTANT_APP_INVALID = -116;
 
+    /**
+     * Installation parse return code: this is passed in the
+     * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the dex metadata file is invalid or
+     * if there was no matching apk file for a dex metadata file.
+     *
+     * @hide
+     */
+    public static final int INSTALL_FAILED_BAD_DEX_METADATA = -117;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "DELETE_" }, value = {
             DELETE_KEEP_DATA,
@@ -5632,6 +5642,8 @@
             case INSTALL_FAILED_DUPLICATE_PERMISSION: return "INSTALL_FAILED_DUPLICATE_PERMISSION";
             case INSTALL_FAILED_NO_MATCHING_ABIS: return "INSTALL_FAILED_NO_MATCHING_ABIS";
             case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED";
+            case INSTALL_FAILED_BAD_DEX_METADATA:
+                return "INSTALL_FAILED_BAD_DEX_METADATA";
             default: return Integer.toString(status);
         }
     }
@@ -5676,6 +5688,7 @@
             case INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: return PackageInstaller.STATUS_FAILURE_INVALID;
             case INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: return PackageInstaller.STATUS_FAILURE_INVALID;
             case INSTALL_PARSE_FAILED_MANIFEST_EMPTY: return PackageInstaller.STATUS_FAILURE_INVALID;
+            case INSTALL_FAILED_BAD_DEX_METADATA: return PackageInstaller.STATUS_FAILURE_INVALID;
             case INSTALL_FAILED_INTERNAL_ERROR: return PackageInstaller.STATUS_FAILURE;
             case INSTALL_FAILED_USER_RESTRICTED: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE;
             case INSTALL_FAILED_DUPLICATE_PERMISSION: return PackageInstaller.STATUS_FAILURE_CONFLICT;
@@ -5877,4 +5890,14 @@
     @SystemApi
     public abstract void registerDexModule(String dexModulePath,
             @Nullable DexModuleRegisterCallback callback);
+
+    /**
+     * Returns the {@link ArtManager} associated with this package manager.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull ArtManager getArtManager() {
+        throw new UnsupportedOperationException("getArtManager not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index cb9ecf3..a3e3354 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -250,6 +250,9 @@
     }
 
     /** @hide */
+    public static final String APK_FILE_EXTENSION = ".apk";
+
+    /** @hide */
     public static class NewPermissionInfo {
         public final String name;
         public final int sdkVersion;
@@ -616,7 +619,7 @@
     }
 
     public static boolean isApkPath(String path) {
-        return path.endsWith(".apk");
+        return path.endsWith(APK_FILE_EXTENSION);
     }
 
     /**
diff --git a/core/java/android/content/pm/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java
new file mode 100644
index 0000000..0753063
--- /dev/null
+++ b/core/java/android/content/pm/dex/ArtManager.java
@@ -0,0 +1,209 @@
+/**
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dex;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Class for retrieving various kinds of information related to the runtime artifacts of
+ * packages that are currently installed on the device.
+ *
+ * @hide
+ */
+@SystemApi
+public class ArtManager {
+    private static final String TAG = "ArtManager";
+
+    /** The snapshot failed because the package was not found. */
+    public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0;
+    /** The snapshot failed because the package code path does not exist. */
+    public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1;
+    /** The snapshot failed because of an internal error (e.g. error during opening profiles). */
+    public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2;
+
+    /** Constant used for applications profiles. */
+    public static final int PROFILE_APPS = 0;
+    /** Constant used for the boot image profile. */
+    public static final int PROFILE_BOOT_IMAGE = 1;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "PROFILE_" }, value = {
+            PROFILE_APPS,
+            PROFILE_BOOT_IMAGE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProfileType {}
+
+
+    private IArtManager mArtManager;
+
+    /**
+     * @hide
+     */
+    public ArtManager(@NonNull IArtManager manager) {
+        mArtManager = manager;
+    }
+
+    /**
+     * Snapshots a runtime profile according to the {@code profileType} parameter.
+     *
+     * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot
+     * the profile for for an apk belonging to the package {@code packageName}.
+     * The apk is identified by {@code codePath}.
+     *
+     * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot
+     * the profile for the boot image. In this case {@code codePath can be null}. The parameters
+     * {@code packageName} and {@code codePath} are ignored.
+     *u
+     * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     *
+     * The result will be posted on the {@code executor} using the given {@code callback}.
+     * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
+     *
+     * This method will throw {@link IllegalStateException} if
+     * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
+     * {@code profileType}.
+     *
+     * @param profileType the type of profile that should be snapshot (boot image or app)
+     * @param packageName the target package name or null if the target is the boot image
+     * @param codePath the code path for which the profile should be retrieved or null if
+     *                 the target is the boot image
+     * @param callback the callback which should be used for the result
+     * @param executor the executor which should be used to post the result
+     */
+    @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
+    public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
+            @Nullable String codePath, @NonNull Executor executor,
+            @NonNull SnapshotRuntimeProfileCallback callback) {
+        Slog.d(TAG, "Requesting profile snapshot for " + packageName + ":" + codePath);
+
+        SnapshotRuntimeProfileCallbackDelegate delegate =
+                new SnapshotRuntimeProfileCallbackDelegate(callback, executor);
+        try {
+            mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Returns true if runtime profiles are enabled for the given type, false otherwise.
+     *
+     * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     *
+     * @param profileType can be either {@link ArtManager#PROFILE_APPS}
+     *                    or {@link ArtManager#PROFILE_BOOT_IMAGE}
+     */
+    @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
+    public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
+        try {
+            return mArtManager.isRuntimeProfilingEnabled(profileType);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        return false;
+    }
+
+    /**
+     * Callback used for retrieving runtime profiles.
+     */
+    public abstract static class SnapshotRuntimeProfileCallback {
+        /**
+         * Called when the profile snapshot finished with success.
+         *
+         * @param profileReadFd the file descriptor that can be used to read the profile. Note that
+         *                      the file might be empty (which is valid profile).
+         */
+        public abstract void onSuccess(ParcelFileDescriptor profileReadFd);
+
+        /**
+         * Called when the profile snapshot finished with an error.
+         *
+         * @param errCode the error code {@see SNAPSHOT_FAILED_PACKAGE_NOT_FOUND,
+         *      SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND, SNAPSHOT_FAILED_INTERNAL_ERROR}.
+         */
+        public abstract void onError(int errCode);
+    }
+
+    private static class SnapshotRuntimeProfileCallbackDelegate
+            extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub {
+        private final ArtManager.SnapshotRuntimeProfileCallback mCallback;
+        private final Executor mExecutor;
+
+        private SnapshotRuntimeProfileCallbackDelegate(
+                ArtManager.SnapshotRuntimeProfileCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onSuccess(final ParcelFileDescriptor profileReadFd) {
+            mExecutor.execute(() -> mCallback.onSuccess(profileReadFd));
+        }
+
+        @Override
+        public void onError(int errCode) {
+            mExecutor.execute(() -> mCallback.onError(errCode));
+        }
+    }
+
+    /**
+     * Return the profile name for the given split. If {@code splitName} is null the
+     * method returns the profile name for the base apk.
+     *
+     * @hide
+     */
+    public static String getProfileName(String splitName) {
+        return splitName == null ? "primary.prof" : splitName + ".split.prof";
+    }
+
+    /**
+     * Return the path to the current profile corresponding to given package and split.
+     *
+     * @hide
+     */
+    public static String getCurrentProfilePath(String packageName, int userId, String splitName) {
+        File profileDir = Environment.getDataProfilesDePackageDirectory(userId, packageName);
+        return new File(profileDir, getProfileName(splitName)).getAbsolutePath();
+    }
+
+    /**
+     * Return the snapshot profile file for the given package and profile name.
+     *
+     * KEEP in sync with installd dexopt.cpp.
+     * TODO(calin): inject the snapshot profile name from PM to avoid the dependency.
+     *
+     * @hide
+     */
+    public static File getProfileSnapshotFileForName(String packageName, String profileName) {
+        File profileDir = Environment.getDataRefProfilesDePackageDirectory(packageName);
+        return new File(profileDir, profileName  + ".snapshot");
+    }
+}
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
new file mode 100644
index 0000000..5d10b88
--- /dev/null
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -0,0 +1,230 @@
+/**
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dex;
+
+import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
+import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
+
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.PackageParserException;
+import android.util.ArrayMap;
+import android.util.jar.StrictJarFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class used to compute and validate the location of dex metadata files.
+ *
+ * @hide
+ */
+public class DexMetadataHelper {
+    private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+
+    private DexMetadataHelper() {}
+
+    /** Return true if the given file is a dex metadata file. */
+    public static boolean isDexMetadataFile(File file) {
+        return isDexMetadataPath(file.getName());
+    }
+
+    /** Return true if the given path is a dex metadata path. */
+    private static boolean isDexMetadataPath(String path) {
+        return path.endsWith(DEX_METADATA_FILE_EXTENSION);
+    }
+
+    /**
+     * Return the size (in bytes) of all dex metadata files associated with the given package.
+     */
+    public static long getPackageDexMetadataSize(PackageLite pkg) {
+        long sizeBytes = 0;
+        Collection<String> dexMetadataList = DexMetadataHelper.getPackageDexMetadata(pkg).values();
+        for (String dexMetadata : dexMetadataList) {
+            sizeBytes += new File(dexMetadata).length();
+        }
+        return sizeBytes;
+    }
+
+    /**
+     * Search for the dex metadata file associated with the given target file.
+     * If it exists, the method returns the dex metadata file; otherwise it returns null.
+     *
+     * Note that this performs a loose matching suitable to be used in the InstallerSession logic.
+     * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
+     * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
+     */
+    public static File findDexMetadataForFile(File targetFile) {
+        String dexMetadataPath = buildDexMetadataPathForFile(targetFile);
+        File dexMetadataFile = new File(dexMetadataPath);
+        return dexMetadataFile.exists() ? dexMetadataFile : null;
+    }
+
+    /**
+     * Return the dex metadata files for the given package as a map
+     * [code path -> dex metadata path].
+     *
+     * NOTE: involves I/O checks.
+     */
+    public static Map<String, String> getPackageDexMetadata(PackageParser.Package pkg) {
+        return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
+    }
+
+    /**
+     * Return the dex metadata files for the given package as a map
+     * [code path -> dex metadata path].
+     *
+     * NOTE: involves I/O checks.
+     */
+    private static Map<String, String> getPackageDexMetadata(PackageLite pkg) {
+        return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
+    }
+
+    /**
+     * Look up the dex metadata files for the given code paths building the map
+     * [code path -> dex metadata].
+     *
+     * For each code path (.apk) the method checks if a matching dex metadata file (.dm) exists.
+     * If it does it adds the pair to the returned map.
+     *
+     * Note that this method will do a loose
+     * matching based on the extension ('foo.dm' will match 'foo.apk' or 'foo').
+     *
+     * This should only be used for code paths extracted from a package structure after the naming
+     * was enforced in the installer.
+     */
+    private static Map<String, String> buildPackageApkToDexMetadataMap(
+            List<String> codePaths) {
+        ArrayMap<String, String> result = new ArrayMap<>();
+        for (int i = codePaths.size() - 1; i >= 0; i--) {
+            String codePath = codePaths.get(i);
+            String dexMetadataPath = buildDexMetadataPathForFile(new File(codePath));
+
+            if (Files.exists(Paths.get(dexMetadataPath))) {
+                result.put(codePath, dexMetadataPath);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return the dex metadata path associated with the given code path.
+     * (replaces '.apk' extension with '.dm')
+     *
+     * @throws IllegalArgumentException if the code path is not an .apk.
+     */
+    public static String buildDexMetadataPathForApk(String codePath) {
+        if (!PackageParser.isApkPath(codePath)) {
+            throw new IllegalStateException(
+                    "Corrupted package. Code path is not an apk " + codePath);
+        }
+        return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
+                + DEX_METADATA_FILE_EXTENSION;
+    }
+
+    /**
+     * Return the dex metadata path corresponding to the given {@code targetFile} using a loose
+     * matching.
+     * i.e. the method will attempt to match the {@code dmFile} regardless of {@code targetFile}
+     * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
+     */
+    private static String buildDexMetadataPathForFile(File targetFile) {
+        return PackageParser.isApkFile(targetFile)
+                ? buildDexMetadataPathForApk(targetFile.getPath())
+                : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION;
+    }
+
+    /**
+     * Validate the dex metadata files installed for the given package.
+     *
+     * @throws PackageParserException in case of errors.
+     */
+    public static void validatePackageDexMetadata(PackageParser.Package pkg)
+            throws PackageParserException {
+        Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
+        for (String dexMetadata : apkToDexMetadataList) {
+            validateDexMetadataFile(dexMetadata);
+        }
+    }
+
+    /**
+     * Validate that the given file is a dex metadata archive.
+     * This is just a sanity validation that the file is a zip archive.
+     *
+     * @throws PackageParserException if the file is not a .dm file.
+     */
+    private static void validateDexMetadataFile(String dmaPath) throws PackageParserException {
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(dmaPath, false, false);
+        } catch (IOException e) {
+            throw new PackageParserException(INSTALL_FAILED_BAD_DEX_METADATA,
+                    "Error opening " + dmaPath, e);
+        } finally {
+            if (jarFile != null) {
+                try {
+                    jarFile.close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Validates that all dex metadata paths in the given list have a matching apk.
+     * (for any foo.dm there should be either a 'foo' of a 'foo.apk' file).
+     * If that's not the case it throws {@code IllegalStateException}.
+     *
+     * This is used to perform a basic sanity check during adb install commands.
+     * (The installer does not support stand alone .dm files)
+     */
+    public static void validateDexPaths(String[] paths) {
+        ArrayList<String> apks = new ArrayList<>();
+        for (int i = 0; i < paths.length; i++) {
+            if (PackageParser.isApkPath(paths[i])) {
+                apks.add(paths[i]);
+            }
+        }
+        ArrayList<String> unmatchedDmFiles = new ArrayList<>();
+        for (int i = 0; i < paths.length; i++) {
+            String dmPath = paths[i];
+            if (isDexMetadataPath(dmPath)) {
+                boolean valid = false;
+                for (int j = apks.size() - 1; j >= 0; j--) {
+                    if (dmPath.equals(buildDexMetadataPathForFile(new File(apks.get(j))))) {
+                        valid = true;
+                        break;
+                    }
+                }
+                if (!valid) {
+                    unmatchedDmFiles.add(dmPath);
+                }
+            }
+        }
+        if (!unmatchedDmFiles.isEmpty()) {
+            throw new IllegalStateException("Unmatched .dm files: " + unmatchedDmFiles);
+        }
+    }
+
+}
diff --git a/core/java/android/content/pm/dex/IArtManager.aidl b/core/java/android/content/pm/dex/IArtManager.aidl
new file mode 100644
index 0000000..6abfdba
--- /dev/null
+++ b/core/java/android/content/pm/dex/IArtManager.aidl
@@ -0,0 +1,58 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm.dex;
+
+import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
+
+/**
+ * A system service that provides access to runtime and compiler artifacts.
+ *
+ * @hide
+ */
+interface IArtManager {
+    /**
+     * Snapshots a runtime profile according to the {@code profileType} parameter.
+     *
+     * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot
+     * the profile for for an apk belonging to the package {@code packageName}.
+     * The apk is identified by {@code codePath}.
+     *
+     * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot
+     * the profile for the boot image. In this case {@code codePath can be null}. The parameters
+     * {@code packageName} and {@code codePath} are ignored.
+     *
+     * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     *
+     * The result will be posted on the {@code executor} using the given {@code callback}.
+     * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
+     *
+     * This method will throw {@link IllegalStateException} if
+     * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
+     * {@code profileType}.
+     */
+    oneway void snapshotRuntimeProfile(int profileType, in String packageName,
+        in String codePath, in ISnapshotRuntimeProfileCallback callback);
+
+     /**
+       * Returns true if runtime profiles are enabled for the given type, false otherwise.
+       * The type can be can be either {@code ArtManager.PROFILE_APPS}
+       * or {@code ArtManager.PROFILE_BOOT_IMAGE}.
+       *
+       * @param profileType
+       */
+    boolean isRuntimeProfilingEnabled(int profileType);
+}
diff --git a/core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl b/core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl
new file mode 100644
index 0000000..3b4838f
--- /dev/null
+++ b/core/java/android/content/pm/dex/ISnapshotRuntimeProfileCallback.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm.dex;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Callback used to post the result of a profile-snapshot operation.
+ *
+ * @hide
+ */
+oneway interface ISnapshotRuntimeProfileCallback {
+    void onSuccess(in ParcelFileDescriptor profileReadFd);
+    void onError(int errCode);
+}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5b0e5bbc..d8eae8c 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -291,7 +291,7 @@
     }
 
     /** {@hide} */
-    public static File getReferenceProfile(String packageName) {
+    public static File getDataRefProfilesDePackageDirectory(String packageName) {
         return buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName);
     }
 
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index e923223..97500f2 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.dex.DexMetadataHelper;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.IBinder;
@@ -663,6 +664,9 @@
             }
         }
 
+        // Include raw dex metadata files
+        sizeBytes += DexMetadataHelper.getPackageDexMetadataSize(pkg);
+
         // Include all relevant native code
         sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride);
 
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 21f1fb6..4a3bb3c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -576,7 +576,7 @@
                     installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
                             instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
                             uuid, classLoaderContext, seInfo, false /* downgrade */,
-                            targetSdkVersion);
+                            targetSdkVersion, /*profileName*/ null, /*dexMetadataPath*/ null);
                 } catch (RemoteException | ServiceSpecificException e) {
                     // Ignore (but log), we need this on the classpath for fallback mode.
                     Log.w(TAG, "Failed compiling classpath element for system server: "
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ae49070..4ea2d10 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3613,6 +3613,11 @@
     <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
         android:protectionLevel="signature|development|instant|appop" />
 
+    <!-- @SystemApi Allows an application to read the runtime profiles of other apps.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.READ_RUNTIME_PROFILES"
+                android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/tests/coretests/apks/install-split-base/Android.mk b/core/tests/coretests/apks/install-split-base/Android.mk
new file mode 100644
index 0000000..5b60e31
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-base/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := install_split_base
+
+include $(FrameworkCoreTests_BUILD_PACKAGE)
\ No newline at end of file
diff --git a/core/tests/coretests/apks/install-split-base/AndroidManifest.xml b/core/tests/coretests/apks/install-split-base/AndroidManifest.xml
new file mode 100644
index 0000000..c2bfedd
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-base/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.install_split"
+        android:isolatedSplits="true">
+
+    <application android:label="ClassloaderSplitApp">
+        <activity android:name=".BaseActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java b/core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
new file mode 100644
index 0000000..cb5760ce
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-base/src/com/google/android/dexapis/splitapp/BaseActivity.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.dexapis.splitapp;
+
+import android.app.Activity;
+
+/** Main activity */
+public class BaseActivity extends Activity {
+}
diff --git a/core/tests/coretests/apks/install-split-feature-a/Android.mk b/core/tests/coretests/apks/install-split-feature-a/Android.mk
new file mode 100644
index 0000000..0f37d16
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-feature-a/Android.mk
@@ -0,0 +1,14 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := install_split_feature_a
+
+LOCAL_USE_AAPT2 := true
+LOCAL_AAPT_FLAGS += --custom-package com.google.android.dexapis.splitapp.feature_a
+LOCAL_AAPT_FLAGS += --package-id 0x80
+
+include $(FrameworkCoreTests_BUILD_PACKAGE)
\ No newline at end of file
diff --git a/core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml b/core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml
new file mode 100644
index 0000000..3221c75
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-feature-a/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.install_split"
+        featureSplit="feature_a">
+
+    <application>
+        <activity android:name=".feature_a.FeatureAActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java b/core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
new file mode 100644
index 0000000..0af5f89
--- /dev/null
+++ b/core/tests/coretests/apks/install-split-feature-a/src/com/google/android/dexapis/splitapp/feature_a/FeatureAActivity.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.dexapis.splitapp.feature_a;
+
+import android.app.Activity;
+
+/** Main activity */
+public class FeatureAActivity extends Activity {
+}
diff --git a/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
new file mode 100644
index 0000000..a183736
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/dex/DexMetadataHelperTest.java
@@ -0,0 +1,235 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.dex;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.ApkLite;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.PackageParser.PackageLite;
+import android.content.pm.PackageParser.PackageParserException;
+import android.os.FileUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.frameworks.coretests.R;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import libcore.io.IoUtils;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DexMetadataHelperTest {
+    private static final String APK_FILE_EXTENSION = ".apk";
+    private static final String DEX_METADATA_FILE_EXTENSION = ".dm";
+
+    private File mTmpDir = null;
+
+    @Before
+    public void setUp() {
+        mTmpDir = IoUtils.createTemporaryDirectory("DexMetadataHelperTest");
+    }
+
+    @After
+    public void tearDown() {
+        if (mTmpDir != null) {
+            File[] files = mTmpDir.listFiles();
+            for (File f : files) {
+                f.delete();
+            }
+        }
+    }
+
+    private File createDexMetadataFile(String apkFileName) throws IOException {
+        File dmFile = new File(mTmpDir, apkFileName.replace(APK_FILE_EXTENSION,
+                DEX_METADATA_FILE_EXTENSION));
+        try (FileOutputStream fos = new FileOutputStream(dmFile)) {
+            try (ZipOutputStream zipOs = new ZipOutputStream(fos)) {
+                zipOs.putNextEntry(new ZipEntry("primary.prof"));
+                zipOs.closeEntry();
+            }
+        }
+        return dmFile;
+    }
+
+    private File copyApkToToTmpDir(String apkFileName, int apkResourceId) throws IOException {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        File outFile = new File(mTmpDir, apkFileName);
+        try (InputStream is = context.getResources().openRawResource(apkResourceId)) {
+            FileUtils.copyToFileOrThrow(is, outFile);
+        }
+        return outFile;
+    }
+
+    @Test
+    public void testParsePackageWithDmFileValid() throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("install_split_base.apk");
+        Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+
+        Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
+        assertEquals(1, packageDexMetadata.size());
+        String baseDexMetadata = packageDexMetadata.get(pkg.baseCodePath);
+        assertNotNull(baseDexMetadata);
+        assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.baseCodePath));
+    }
+
+    @Test
+    public void testParsePackageSplitsWithDmFileValid()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+        createDexMetadataFile("install_split_base.apk");
+        createDexMetadataFile("install_split_feature_a.apk");
+        Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+
+        Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
+        assertEquals(2, packageDexMetadata.size());
+        String baseDexMetadata = packageDexMetadata.get(pkg.baseCodePath);
+        assertNotNull(baseDexMetadata);
+        assertTrue(isDexMetadataForApk(baseDexMetadata, pkg.baseCodePath));
+
+        String splitDexMetadata = packageDexMetadata.get(pkg.splitCodePaths[0]);
+        assertNotNull(splitDexMetadata);
+        assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.splitCodePaths[0]));
+    }
+
+    @Test
+    public void testParsePackageSplitsNoBaseWithDmFileValid()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+        createDexMetadataFile("install_split_feature_a.apk");
+        Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+
+        Map<String, String> packageDexMetadata = DexMetadataHelper.getPackageDexMetadata(pkg);
+        assertEquals(1, packageDexMetadata.size());
+
+        String splitDexMetadata = packageDexMetadata.get(pkg.splitCodePaths[0]);
+        assertNotNull(splitDexMetadata);
+        assertTrue(isDexMetadataForApk(splitDexMetadata, pkg.splitCodePaths[0]));
+    }
+
+    @Test
+    public void testParsePackageWithDmFileInvalid() throws IOException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
+        Files.createFile(invalidDmFile.toPath());
+        try {
+            PackageParser.Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+            DexMetadataHelper.validatePackageDexMetadata(pkg);
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testParsePackageSplitsWithDmFileInvalid()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+        createDexMetadataFile("install_split_base.apk");
+        File invalidDmFile = new File(mTmpDir, "install_split_feature_a.dm");
+        Files.createFile(invalidDmFile.toPath());
+
+        try {
+            PackageParser.Package pkg = new PackageParser().parsePackage(mTmpDir, 0 /* flags */);
+            DexMetadataHelper.validatePackageDexMetadata(pkg);
+        } catch (PackageParserException e) {
+            assertEquals(e.error, PackageManager.INSTALL_FAILED_BAD_DEX_METADATA);
+        }
+    }
+
+    @Test
+    public void testPackageWithDmFileNoMatch() throws IOException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        createDexMetadataFile("non_existent.apk");
+
+        try {
+            DexMetadataHelper.validateDexPaths(mTmpDir.list());
+            fail("Should fail validation");
+        } catch (IllegalStateException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testPackageSplitsWithDmFileNoMatch()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
+        createDexMetadataFile("install_split_base.apk");
+        createDexMetadataFile("install_split_feature_a.mistake.apk");
+
+        try {
+            DexMetadataHelper.validateDexPaths(mTmpDir.list());
+            fail("Should fail validation");
+        } catch (IllegalStateException e) {
+            // expected.
+        }
+    }
+
+    @Test
+    public void testPackageSizeWithDmFile()
+            throws IOException, PackageParserException {
+        copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
+        File dm = createDexMetadataFile("install_split_base.apk");
+        PackageParser.PackageLite pkg = new PackageParser().parsePackageLite(mTmpDir,
+                0 /* flags */);
+
+        Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg));
+    }
+
+    // This simulates the 'adb shell pm install' flow.
+    @Test
+    public void testPackageSizeWithPartialPackageLite() throws IOException, PackageParserException {
+        File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base);
+        File dm = createDexMetadataFile("install_split_base.apk");
+        ApkLite baseApk = PackageParser.parseApkLite(base, 0);
+        PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+                null, null);
+        Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite));
+
+    }
+
+    private static boolean isDexMetadataForApk(String dmaPath, String apkPath) {
+        return apkPath.substring(0, apkPath.length() - APK_FILE_EXTENSION.length()).equals(
+                dmaPath.substring(0, dmaPath.length() - DEX_METADATA_FILE_EXTENSION.length()));
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9194682..a52bd4e 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -16,7 +16,9 @@
 
 package com.android.server.pm;
 
+import android.annotation.AppIdInt;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.Build;
@@ -283,43 +285,45 @@
     public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
-            @Nullable String seInfo, boolean downgrade, int targetSdkVersion)
+            @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
+            @Nullable String profileName, @Nullable String dexMetadataPath)
             throws InstallerException {
         assertValidInstructionSet(instructionSet);
         if (!checkBeforeRemote()) return;
         try {
             mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
                     dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
-                    targetSdkVersion);
+                    targetSdkVersion, profileName, dexMetadataPath);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public boolean mergeProfiles(int uid, String packageName) throws InstallerException {
-        if (!checkBeforeRemote()) return false;
-        try {
-            return mInstalld.mergeProfiles(uid, packageName);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
-    public boolean dumpProfiles(int uid, String packageName, String codePaths)
+    public boolean mergeProfiles(int uid, String packageName, String profileName)
             throws InstallerException {
         if (!checkBeforeRemote()) return false;
         try {
-            return mInstalld.dumpProfiles(uid, packageName, codePaths);
+            return mInstalld.mergeProfiles(uid, packageName, profileName);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
     }
 
-    public boolean copySystemProfile(String systemProfile, int uid, String packageName)
+    public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath)
             throws InstallerException {
         if (!checkBeforeRemote()) return false;
         try {
-            return mInstalld.copySystemProfile(systemProfile, uid, packageName);
+            return mInstalld.dumpProfiles(uid, packageName, profileName, codePath);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public boolean copySystemProfile(String systemProfile, int uid, String packageName,
+                String profileName) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+        try {
+            return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
@@ -363,10 +367,10 @@
         }
     }
 
-    public void clearAppProfiles(String packageName) throws InstallerException {
+    public void clearAppProfiles(String packageName, String profileName) throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            mInstalld.clearAppProfiles(packageName);
+            mInstalld.clearAppProfiles(packageName, profileName);
         } catch (Exception e) {
             throw InstallerException.from(e);
         }
@@ -489,6 +493,36 @@
         }
     }
 
+    public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
+            @Nullable String volumeUuid, int flags) throws InstallerException {
+        if (!checkBeforeRemote()) return new byte[0];
+        try {
+            return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public boolean createProfileSnapshot(int appId, String packageName, String profileName,
+            String classpath) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+        try {
+            return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    public void destroyProfileSnapshot(String packageName, String profileName)
+            throws InstallerException {
+        if (!checkBeforeRemote()) return;
+        try {
+            mInstalld.destroyProfileSnapshot(packageName, profileName);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     public void invalidateMounts() throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
@@ -507,6 +541,17 @@
         }
     }
 
+    public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
+            String profileName, String codePath, String dexMetadataPath) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+        try {
+            return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
+                    dexMetadataPath);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     private static void assertValidInstructionSet(String instructionSet)
             throws InstallerException {
         for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 5dbd3ca..232743c 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -261,12 +261,12 @@
                     String instructionSet, int dexoptNeeded, @Nullable String outputPath,
                     int dexFlags, String compilerFilter, @Nullable String volumeUuid,
                     @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
-                    int targetSdkVersion)
-                    throws InstallerException {
+                    int targetSdkVersion, @Nullable String profileName,
+                    @Nullable String dexMetadataPath) throws InstallerException {
                 final StringBuilder builder = new StringBuilder();
 
-                // The version. Right now it's 4.
-                builder.append("4 ");
+                // The version. Right now it's 6.
+                builder.append("6 ");
 
                 builder.append("dexopt");
 
@@ -283,6 +283,8 @@
                 encodeParameter(builder, seInfo);
                 encodeParameter(builder, downgrade);
                 encodeParameter(builder, targetSdkVersion);
+                encodeParameter(builder, profileName);
+                encodeParameter(builder, dexMetadataPath);
 
                 commands.add(builder.toString());
             }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 77fe8bc..126ee80 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
+import android.content.pm.dex.ArtManager;
+import android.content.pm.dex.DexMetadataHelper;
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -211,12 +213,21 @@
                 }
             }
 
+            String profileName = ArtManager.getProfileName(i == 0 ? null : pkg.splitNames[i - 1]);
+
+            String dexMetadataPath = null;
+            if (options.isDexoptInstallWithDexMetadata()) {
+                File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(new File(path));
+                dexMetadataPath = dexMetadataFile == null
+                        ? null : dexMetadataFile.getAbsolutePath();
+            }
+
             final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
                     || packageUseInfo.isUsedByOtherApps(path);
             final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
                 options.getCompilerFilter(), isUsedByOtherApps);
             final boolean profileUpdated = options.isCheckForProfileUpdates() &&
-                isProfileUpdated(pkg, sharedGid, compilerFilter);
+                isProfileUpdated(pkg, sharedGid, profileName, compilerFilter);
 
             // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
             // flags.
@@ -225,7 +236,7 @@
             for (String dexCodeIsa : dexCodeInstructionSets) {
                 int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
                         profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
-                        packageStats, options.isDowngrade());
+                        packageStats, options.isDowngrade(), profileName, dexMetadataPath);
                 // The end result is:
                 //  - FAILED if any path failed,
                 //  - PERFORMED if at least one path needed compilation,
@@ -249,7 +260,8 @@
     @GuardedBy("mInstallLock")
     private int dexOptPath(PackageParser.Package pkg, String path, String isa,
             String compilerFilter, boolean profileUpdated, String classLoaderContext,
-            int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
+            int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
+            String profileName, String dexMetadataPath) {
         int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
                 profileUpdated, downgrade);
         if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
@@ -275,7 +287,8 @@
             // primary dex files.
             mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
                     compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
-                    false /* downgrade*/, pkg.applicationInfo.targetSdkVersion);
+                    false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
+                    profileName, dexMetadataPath);
 
             if (packageStats != null) {
                 long endTime = System.currentTimeMillis();
@@ -396,7 +409,8 @@
                 mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                         /*oatDir*/ null, dexoptFlags,
                         compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
-                        options.isDowngrade(), info.targetSdkVersion);
+                        options.isDowngrade(), info.targetSdkVersion, /*profileName*/ null,
+                        /*dexMetadataPath*/ null);
             }
 
             return DEX_OPT_PERFORMED;
@@ -506,9 +520,13 @@
     private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
         int flags = info.flags;
         boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
-        // Profile guide compiled oat files should not be public.
+        // Profile guide compiled oat files should not be public unles they are based
+        // on profiles from dex metadata archives.
+        // The flag isDexoptInstallWithDexMetadata applies only on installs when we know that
+        // the user does not have an existing profile.
         boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
-        boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
+        boolean isPublic = !info.isForwardLocked() &&
+                (!isProfileGuidedFilter || options.isDexoptInstallWithDexMetadata());
         int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
         // Some apps are executed with restrictions on hidden API usage. If this app is one
         // of them, pass a flag to dexopt to enable the same restrictions during compilation.
@@ -548,14 +566,15 @@
      * current profile and the reference profile will be merged and subsequent calls
      * may return a different result.
      */
-    private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String compilerFilter) {
+    private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String profileName,
+            String compilerFilter) {
         // Check if we are allowed to merge and if the compiler filter is profile guided.
         if (!isProfileGuidedCompilerFilter(compilerFilter)) {
             return false;
         }
         // Merge profiles. It returns whether or not there was an updated in the profile info.
         try {
-            return mInstaller.mergeProfiles(uid, pkg.packageName);
+            return mInstaller.mergeProfiles(uid, pkg.packageName, profileName);
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to merge profiles", e);
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index bf86300..7204aa1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -17,10 +17,12 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED;
+import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
 import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_WRONLY;
@@ -94,6 +96,7 @@
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
 
+import android.content.pm.dex.DexMetadataHelper;
 import libcore.io.IoUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -259,6 +262,7 @@
             // entries like "lost+found".
             if (file.isDirectory()) return false;
             if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
+            if (DexMetadataHelper.isDexMetadataFile(file)) return false;
             return true;
         }
     };
@@ -944,6 +948,15 @@
                 mInstallerPackageName, mInstallerUid, user, mCertificates);
     }
 
+    private static void maybeRenameFile(File from, File to) throws PackageManagerException {
+        if (!from.equals(to)) {
+            if (!from.renameTo(to)) {
+                throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                        "Could not rename file " + from + " to " + to);
+            }
+        }
+    }
+
     /**
      * Validate install by confirming that all application packages are have
      * consistent package name, version code, and signing certificates.
@@ -988,6 +1001,7 @@
         if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
         }
+
         // Verify that all staged packages are internally consistent
         final ArraySet<String> stagedSplits = new ArraySet<>();
         for (File addedFile : addedFiles) {
@@ -1022,9 +1036,9 @@
             // Take this opportunity to enforce uniform naming
             final String targetName;
             if (apk.splitName == null) {
-                targetName = "base.apk";
+                targetName = "base" + APK_FILE_EXTENSION;
             } else {
-                targetName = "split_" + apk.splitName + ".apk";
+                targetName = "split_" + apk.splitName + APK_FILE_EXTENSION;
             }
             if (!FileUtils.isValidExtFilename(targetName)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -1032,9 +1046,7 @@
             }
 
             final File targetFile = new File(mResolvedStageDir, targetName);
-            if (!addedFile.equals(targetFile)) {
-                addedFile.renameTo(targetFile);
-            }
+            maybeRenameFile(addedFile, targetFile);
 
             // Base is coming from session
             if (apk.splitName == null) {
@@ -1042,6 +1054,18 @@
             }
 
             mResolvedStagedFiles.add(targetFile);
+
+            final File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(addedFile);
+            if (dexMetadataFile != null) {
+                if (!FileUtils.isValidExtFilename(dexMetadataFile.getName())) {
+                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                            "Invalid filename: " + dexMetadataFile);
+                }
+                final File targetDexMetadataFile = new File(mResolvedStageDir,
+                        DexMetadataHelper.buildDexMetadataPathForApk(targetName));
+                mResolvedStagedFiles.add(targetDexMetadataFile);
+                maybeRenameFile(dexMetadataFile, targetDexMetadataFile);
+            }
         }
 
         if (removeSplitList.size() > 0) {
@@ -1099,6 +1123,12 @@
             if (mResolvedBaseFile == null) {
                 mResolvedBaseFile = new File(appInfo.getBaseCodePath());
                 mResolvedInheritedFiles.add(mResolvedBaseFile);
+                // Inherit the dex metadata if present.
+                final File baseDexMetadataFile =
+                        DexMetadataHelper.findDexMetadataForFile(mResolvedBaseFile);
+                if (baseDexMetadataFile != null) {
+                    mResolvedInheritedFiles.add(baseDexMetadataFile);
+                }
             }
 
             // Inherit splits if not overridden
@@ -1109,6 +1139,12 @@
                     final boolean splitRemoved = removeSplitList.contains(splitName);
                     if (!stagedSplits.contains(splitName) && !splitRemoved) {
                         mResolvedInheritedFiles.add(splitFile);
+                        // Inherit the dex metadata if present.
+                        final File splitDexMetadataFile =
+                                DexMetadataHelper.findDexMetadataForFile(splitFile);
+                        if (splitDexMetadataFile != null) {
+                            mResolvedInheritedFiles.add(splitDexMetadataFile);
+                        }
                     }
                 }
             }
@@ -1167,7 +1203,7 @@
     /**
      * Calculate the final install footprint size, combining both staged and
      * existing APKs together and including unpacked native code from both.
-     */
+    */
     private long calculateInstalledSize() throws PackageManagerException {
         Preconditions.checkNotNull(mResolvedBaseFile);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4454c71..3466f55 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -106,8 +106,6 @@
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
 import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
 
-import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
-
 import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -179,6 +177,9 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VerifierInfo;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.ArtManager;
+import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.dex.IArtManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -285,13 +286,14 @@
 import com.android.server.pm.PermissionsState.PermissionState;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
+import com.android.server.pm.dex.ArtManagerService;
+import com.android.server.pm.dex.DexLogger;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
-import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
@@ -960,6 +962,8 @@
 
     final PackageInstallerService mInstallerService;
 
+    final ArtManagerService mArtManagerService;
+
     private final PackageDexOptimizer mPackageDexOptimizer;
     // DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
     // is used by other apps).
@@ -2461,7 +2465,11 @@
         mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
-        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
+        DexManager.Listener dexManagerListener = DexLogger.getListener(this,
+                installer, mInstallLock);
+        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
+                dexManagerListener);
+        mArtManagerService = new ArtManagerService(this, installer, mInstallLock);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -9753,7 +9761,8 @@
                         // PackageDexOptimizer to prevent this happening on first boot. The issue
                         // is that we don't have a good way to say "do this only once".
                         if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
-                                pkg.applicationInfo.uid, pkg.packageName)) {
+                                pkg.applicationInfo.uid, pkg.packageName,
+                                ArtManager.getProfileName(null))) {
                             Log.e(TAG, "Installer failed to copy system profile!");
                         } else {
                             // Disabled as this causes speed-profile compilation during first boot
@@ -9788,7 +9797,8 @@
                                 // issue is that we don't have a good way to say "do this only
                                 // once".
                                 if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
-                                        pkg.applicationInfo.uid, pkg.packageName)) {
+                                        pkg.applicationInfo.uid, pkg.packageName,
+                                        ArtManager.getProfileName(null))) {
                                     Log.e(TAG, "Failed to copy system profile for stub package!");
                                 } else {
                                     useProfileForDexopt = true;
@@ -10213,14 +10223,7 @@
 
         synchronized (mInstallLock) {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles");
-            final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-            try {
-                List<String> allCodePaths = pkg.getAllCodePathsExcludingResourceOnly();
-                String codePaths = TextUtils.join(";", allCodePaths);
-                mInstaller.dumpProfiles(sharedGid, packageName, codePaths);
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to dump profiles", e);
-            }
+            mArtManagerService.dumpProfiles(pkg);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
@@ -10296,6 +10299,8 @@
         for (int i = 0; i < childCount; i++) {
             clearAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
         }
+
+        clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
     }
 
     private void clearAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
@@ -10368,18 +10373,10 @@
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return;
         }
-        clearAppProfilesLeafLIF(pkg);
+        mArtManagerService.clearAppProfiles(pkg);
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
-            clearAppProfilesLeafLIF(pkg.childPackages.get(i));
-        }
-    }
-
-    private void clearAppProfilesLeafLIF(PackageParser.Package pkg) {
-        try {
-            mInstaller.clearAppProfiles(pkg.packageName);
-        } catch (InstallerException e) {
-            Slog.w(TAG, String.valueOf(e));
+            mArtManagerService.clearAppProfiles(pkg.childPackages.get(i));
         }
     }
 
@@ -17901,7 +17898,6 @@
 
             clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
                     | StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-            clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
 
             try {
                 final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags,
@@ -18037,7 +18033,6 @@
         // Successfully disabled the old package. Now proceed with re-installation
         clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
                 | StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-        clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
 
         res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
         pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP,
@@ -18463,6 +18458,7 @@
         final PackageParser.Package pkg;
         try {
             pkg = pp.parsePackage(tmpPackageFile, parseFlags);
+            DexMetadataHelper.validatePackageDexMetadata(pkg);
         } catch (PackageParserException e) {
             res.setError("Failed parse during installPackageLI", e);
             return;
@@ -18837,6 +18833,11 @@
             }
         }
 
+        // Prepare the application profiles for the new code paths.
+        // This needs to be done before invoking dexopt so that any install-time profile
+        // can be used for optimizations.
+        mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier()));
+
         // Check whether we need to dexopt the app.
         //
         // NOTE: it is IMPORTANT to call dexopt:
@@ -18871,7 +18872,8 @@
             // Also, don't fail application installs if the dexopt step fails.
             DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName,
                     REASON_INSTALL,
-                    DexoptOptions.DEXOPT_BOOT_COMPLETE);
+                    DexoptOptions.DEXOPT_BOOT_COMPLETE |
+                    DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE);
             mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
                     null /* instructionSets */,
                     getOrCreateCompilerPackageStats(pkg),
@@ -22051,7 +22053,6 @@
                     }
                     clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
                             | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-                    clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
                     mDexManager.notifyPackageUpdated(newPkg.packageName,
                             newPkg.baseCodePath, newPkg.splitCodePaths);
                 }
@@ -24211,6 +24212,8 @@
                 Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
             }
         }
+        // Prepare the application profiles.
+        mArtManagerService.prepareAppProfiles(pkg, userId);
 
         if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
             // TODO: mark this structure as dirty so we persist it!
@@ -24907,6 +24910,11 @@
         return mInstallerService;
     }
 
+    @Override
+    public IArtManager getArtManager() {
+        return mArtManagerService;
+    }
+
     private boolean userNeedsBadging(int userId) {
         int index = mUserNeedsBadging.indexOfKey(userId);
         if (index < 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 806abec..3857c96 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -42,6 +42,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.DexMetadataHelper;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
@@ -1480,6 +1481,14 @@
             session = new PackageInstaller.Session(
                     mInterface.getPackageInstaller().openSession(sessionId));
 
+            // Sanity check that all .dm files match an apk.
+            // (The installer does not support standalone .dm files and will not process them.)
+            try {
+                DexMetadataHelper.validateDexPaths(session.getNames());
+            } catch (IllegalStateException | IOException e) {
+                pw.println("Warning [Could not validate the dex paths: " + e.getMessage() + "]");
+            }
+
             final LocalIntentReceiver receiver = new LocalIntentReceiver();
             session.commit(receiver.getIntentSender());
 
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
new file mode 100644
index 0000000..e290272
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import android.Manifest;
+import android.annotation.UserIdInt;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.dex.ArtManager;
+import android.content.pm.dex.ArtManager.ProfileType;
+import android.content.pm.dex.DexMetadataHelper;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.content.pm.IPackageManager;
+import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.system.Os;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import libcore.io.IoUtils;
+import libcore.util.NonNull;
+import libcore.util.Nullable;
+
+/**
+ * A system service that provides access to runtime and compiler artifacts.
+ *
+ * This service is not accessed by users directly, instead one uses an instance of
+ * {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
+ * <p/>
+ * {@code context().getPackageManager().getArtManager();}
+ * <p class="note">
+ * Note: Accessing runtime artifacts may require extra permissions. For example querying the
+ * runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
+ * which is a system-level permission that will not be granted to normal apps.
+ */
+public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
+    private static final String TAG = "ArtManagerService";
+
+    private static boolean DEBUG = false;
+    private static boolean DEBUG_IGNORE_PERMISSIONS = false;
+
+    // Package name used to create the profile directory layout when
+    // taking a snapshot of the boot image profile.
+    private static final String BOOT_IMAGE_ANDROID_PACKAGE = "android";
+    // Profile name used for the boot image profile.
+    private static final String BOOT_IMAGE_PROFILE_NAME = "android.prof";
+
+    private final IPackageManager mPackageManager;
+    private final Object mInstallLock;
+    @GuardedBy("mInstallLock")
+    private final Installer mInstaller;
+
+    private final Handler mHandler;
+
+    public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
+        mPackageManager = pm;
+        mInstaller = installer;
+        mInstallLock = installLock;
+        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
+    }
+
+    @Override
+    public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
+            @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback) {
+        // Sanity checks on the arguments.
+        Preconditions.checkNotNull(callback);
+
+        boolean bootImageProfile = profileType == ArtManager.PROFILE_BOOT_IMAGE;
+        if (!bootImageProfile) {
+            Preconditions.checkStringNotEmpty(codePath);
+            Preconditions.checkStringNotEmpty(packageName);
+        }
+
+        // Verify that the caller has the right permissions and that the runtime profiling is
+        // enabled. The call to isRuntimePermissions will checkReadRuntimeProfilePermission.
+        if (!isRuntimeProfilingEnabled(profileType)) {
+            throw new IllegalStateException("Runtime profiling is not enabled for " + profileType);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
+        }
+
+        if (bootImageProfile) {
+            snapshotBootImageProfile(callback);
+        } else {
+            snapshotAppProfile(packageName, codePath, callback);
+        }
+    }
+
+    private void snapshotAppProfile(String packageName, String codePath,
+            ISnapshotRuntimeProfileCallback callback) {
+        PackageInfo info = null;
+        try {
+            // Note that we use the default user 0 to retrieve the package info.
+            // This doesn't really matter because for user 0 we always get a package back (even if
+            // it's not installed for the user 0). It is ok because we only care about the code
+            // paths and not if the package is enabled or not for the user.
+
+            // TODO(calin): consider adding an API to PMS which can retrieve the
+            // PackageParser.Package.
+            info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
+        } catch (RemoteException ignored) {
+            // Should not happen.
+        }
+        if (info == null) {
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
+            return;
+        }
+
+        boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
+        String splitName = null;
+        String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
+        if (!pathFound && (splitCodePaths != null)) {
+            for (int i = splitCodePaths.length - 1; i >= 0; i--) {
+                if (splitCodePaths[i].equals(codePath)) {
+                    pathFound = true;
+                    splitName = info.applicationInfo.splitNames[i];
+                    break;
+                }
+            }
+        }
+        if (!pathFound) {
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
+            return;
+        }
+
+        // All good, create the profile snapshot.
+        int appId = UserHandle.getAppId(info.applicationInfo.uid);
+        if (appId < 0) {
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+            Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
+            return;
+        }
+
+        createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
+                appId, callback);
+        // Destroy the snapshot, we no longer need it.
+        destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+    }
+
+    private void createProfileSnapshot(String packageName, String profileName, String classpath,
+            int appId, ISnapshotRuntimeProfileCallback callback) {
+        // Ask the installer to snapshot the profile.
+        synchronized (mInstallLock) {
+            try {
+                if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
+                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+                    return;
+                }
+            } catch (InstallerException e) {
+                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+                return;
+            }
+        }
+
+        // Open the snapshot and invoke the callback.
+        File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
+
+        ParcelFileDescriptor fd = null;
+        try {
+            fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
+            postSuccess(packageName, fd, callback);
+        } catch (FileNotFoundException e) {
+            Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
+                    + snapshotProfile, e);
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+        } finally {
+            IoUtils.closeQuietly(fd);
+        }
+    }
+
+    private void destroyProfileSnapshot(String packageName, String profileName) {
+        if (DEBUG) {
+            Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
+        }
+
+        synchronized (mInstallLock) {
+            try {
+                mInstaller.destroyProfileSnapshot(packageName, profileName);
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Failed to destroy profile snapshot for " +
+                    packageName + ":" + profileName, e);
+            }
+        }
+    }
+
+    @Override
+    public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
+        // Verify that the caller has the right permissions.
+        checkReadRuntimeProfilePermission();
+
+        switch (profileType) {
+            case ArtManager.PROFILE_APPS :
+                return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+            case ArtManager.PROFILE_BOOT_IMAGE:
+                return (Build.IS_USERDEBUG || Build.IS_ENG) &&
+                        SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
+                        SystemProperties.getBoolean("dalvik.vm.profilebootimage", false);
+            default:
+                throw new IllegalArgumentException("Invalid profile type:" + profileType);
+        }
+    }
+
+    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
+        // Combine the profiles for boot classpath and system server classpath.
+        // This avoids having yet another type of profiles and simplifies the processing.
+        String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
+                Os.getenv("SYSTEMSERVERCLASSPATH"));
+
+        // Create the snapshot.
+        createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
+                /*appId*/ -1, callback);
+        // Destroy the snapshot, we no longer need it.
+        destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
+    }
+
+    /**
+     * Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
+     * on the internal {@code mHandler}.
+     */
+    private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
+            int errCode) {
+        if (DEBUG) {
+            Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
+                    errCode);
+        }
+        mHandler.post(() -> {
+            try {
+                callback.onError(errCode);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
+            }
+        });
+    }
+
+    private void postSuccess(String packageName, ParcelFileDescriptor fd,
+            ISnapshotRuntimeProfileCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "Successfully snapshot profile for " + packageName);
+        }
+        mHandler.post(() -> {
+            try {
+                callback.onSuccess(fd);
+            } catch (RemoteException e) {
+                Slog.w(TAG,
+                        "Failed to call onSuccess after profile snapshot for " + packageName, e);
+            }
+        });
+    }
+
+    /**
+     * Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
+     * If not, it throws a {@link SecurityException}.
+     */
+    private void checkReadRuntimeProfilePermission() {
+        if (DEBUG_IGNORE_PERMISSIONS) {
+            return;
+        }
+        try {
+            int result = mPackageManager.checkUidPermission(
+                    Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
+            if (result != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("You need "
+                        + Manifest.permission.READ_RUNTIME_PROFILES
+                        + " permission to snapshot profiles.");
+            }
+        } catch (RemoteException e) {
+            // Should not happen.
+        }
+    }
+
+    /**
+     * Prepare the application profiles.
+     * For all code paths:
+     *   - create the current primary profile to save time at app startup time.
+     *   - copy the profiles from the associated dex metadata file to the reference profile.
+     */
+    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
+        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+        if (user < 0) {
+            Slog.wtf(TAG, "Invalid user id: " + user);
+            return;
+        }
+        if (appId < 0) {
+            Slog.wtf(TAG, "Invalid app id: " + appId);
+            return;
+        }
+        try {
+            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
+            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
+                String codePath = codePathsProfileNames.keyAt(i);
+                String profileName = codePathsProfileNames.valueAt(i);
+                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
+                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
+                synchronized (mInstaller) {
+                    boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
+                            profileName, codePath, dexMetadataPath);
+                    if (!result) {
+                        Slog.e(TAG, "Failed to prepare profile for " +
+                                pkg.packageName + ":" + codePath);
+                    }
+                }
+            }
+        } catch (InstallerException e) {
+            Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
+        }
+    }
+
+    /**
+     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
+     */
+    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
+        for (int i = 0; i < user.length; i++) {
+            prepareAppProfiles(pkg, user[i]);
+        }
+    }
+
+    /**
+     * Clear the profiles for the given package.
+     */
+    public void clearAppProfiles(PackageParser.Package pkg) {
+        try {
+            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
+            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
+                String profileName = packageProfileNames.valueAt(i);
+                mInstaller.clearAppProfiles(pkg.packageName, profileName);
+            }
+        } catch (InstallerException e) {
+            Slog.w(TAG, String.valueOf(e));
+        }
+    }
+
+    /**
+     * Dumps the profiles for the given package.
+     */
+    public void dumpProfiles(PackageParser.Package pkg) {
+        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+        try {
+            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
+            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
+                String codePath = packageProfileNames.keyAt(i);
+                String profileName = packageProfileNames.valueAt(i);
+                synchronized (mInstallLock) {
+                    mInstaller.dumpProfiles(sharedGid, pkg.packageName, profileName, codePath);
+                }
+            }
+        } catch (InstallerException e) {
+            Slog.w(TAG, "Failed to dump profiles", e);
+        }
+    }
+
+    /**
+     * Build the profiles names for all the package code paths (excluding resource only paths).
+     * Return the map [code path -> profile name].
+     */
+    private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
+        ArrayMap<String, String> result = new ArrayMap<>();
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+            result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
+        }
+        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+                if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
+                    result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/dex/DexLogger.java b/services/core/java/com/android/server/pm/dex/DexLogger.java
new file mode 100644
index 0000000..c7bbf1c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexLogger.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm.dex;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+
+import android.util.ArraySet;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+
+import java.io.File;
+import java.util.Set;
+
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
+/**
+ * This class is responsible for logging data about secondary dex files.
+ * The data logged includes hashes of the name and content of each file.
+ */
+public class DexLogger implements DexManager.Listener {
+    private static final String TAG = "DexLogger";
+
+    // Event log tag & subtag used for SafetyNet logging of dynamic
+    // code loading (DCL) - see b/63927552.
+    private static final int SNET_TAG = 0x534e4554;
+    private static final String DCL_SUBTAG = "dcl";
+
+    private final IPackageManager mPackageManager;
+    private final Object mInstallLock;
+    @GuardedBy("mInstallLock")
+    private final Installer mInstaller;
+
+    public static DexManager.Listener getListener(IPackageManager pms,
+            Installer installer, Object installLock) {
+        return new DexLogger(pms, installer, installLock);
+    }
+
+    private DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+      mPackageManager = pms;
+      mInstaller = installer;
+      mInstallLock = installLock;
+    }
+
+    /**
+     * Compute and log hashes of the name and content of a secondary dex file.
+     */
+    @Override
+    public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+            String dexPath, int storageFlags) {
+        int ownerUid = appInfo.uid;
+
+        byte[] hash = null;
+        synchronized(mInstallLock) {
+            try {
+                hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
+                        ownerUid, appInfo.volumeUuid, storageFlags);
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
+                        " : " + e.getMessage());
+            }
+        }
+        if (hash == null) {
+            return;
+        }
+
+        String dexFileName = new File(dexPath).getName();
+        String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
+        // Valid SHA256 will be 256 bits, 32 bytes.
+        if (hash.length == 32) {
+            message = message + ' ' + ByteStringUtils.toHexString(hash);
+        }
+
+        EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, ownerUid, message);
+
+        if (dexUseInfo.isUsedByOtherApps()) {
+            Set<String> otherPackages = dexUseInfo.getLoadingPackages();
+            Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
+            for (String otherPackageName : otherPackages) {
+                try {
+                    int otherUid = mPackageManager.getPackageUid(
+                        otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
+                    if (otherUid != -1 && otherUid != ownerUid) {
+                        otherUids.add(otherUid);
+                    }
+                } catch (RemoteException ignore) {
+                    // Can't happen, we're local.
+                }
+            }
+            for (int otherUid : otherUids) {
+                EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, otherUid, message);
+            }
+        }
+    }
+}
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 6274754..0e2730c 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -76,6 +76,7 @@
     private final Object mInstallLock;
     @GuardedBy("mInstallLock")
     private final Installer mInstaller;
+    private final Listener mListener;
 
     // Possible outcomes of a dex search.
     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
@@ -96,14 +97,24 @@
      */
     private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
 
+    public interface Listener {
+        /**
+         * Invoked just before the secondary dex file {@code dexPath} for the specified application
+         * is reconciled.
+         */
+        void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+                String dexPath, int storageFlags);
+    }
+
     public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
-            Installer installer, Object installLock) {
+            Installer installer, Object installLock, Listener listener) {
       mPackageCodeLocationsCache = new HashMap<>();
       mPackageDexUsage = new PackageDexUsage();
       mPackageManager = pms;
       mPackageDexOptimizer = pdo;
       mInstaller = installer;
       mInstallLock = installLock;
+      mListener = listener;
     }
 
     /**
@@ -389,7 +400,7 @@
                 : mPackageDexOptimizer;
         String packageName = options.getPackageName();
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
             }
@@ -433,7 +444,7 @@
      */
     public void reconcileSecondaryDexFiles(String packageName) {
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
             }
@@ -481,12 +492,16 @@
                 continue;
             }
 
+            if (mListener != null) {
+                mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
+            }
+
             boolean dexStillExists = true;
             synchronized(mInstallLock) {
                 try {
                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
-                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+                            info.uid, isas, info.volumeUuid, flags);
                 } catch (InstallerException e) {
                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
                             " : " + e.getMessage());
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 0966770..d4f95cb 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -59,6 +59,10 @@
     // When set, indicates that dexopt is invoked from the background service.
     public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
 
+    // When set, indicates that dexopt is invoked from the install time flow and
+    // should get the dex metdata file if present.
+    public static final int DEXOPT_INSTALL_WITH_DEX_METADATA_FILE = 1 << 10;
+
     // The name of package to optimize.
     private final String mPackageName;
 
@@ -90,7 +94,8 @@
                 DEXOPT_ONLY_SHARED_DEX |
                 DEXOPT_DOWNGRADE |
                 DEXOPT_AS_SHARED_LIBRARY |
-                DEXOPT_IDLE_BACKGROUND_JOB;
+                DEXOPT_IDLE_BACKGROUND_JOB |
+                DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
         if ((flags & (~validityMask)) != 0) {
             throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
         }
@@ -141,6 +146,10 @@
         return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
     }
 
+    public boolean isDexoptInstallWithDexMetadata() {
+        return (mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) != 0;
+    }
+
     public String getSplitName() {
         return mSplitName;
     }
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 4db9a30..36d0c8b 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
@@ -17,12 +17,15 @@
 package com.android.server.pm.dex;
 
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.os.Build;
 import android.os.UserHandle;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.server.pm.Installer;
+
 import dalvik.system.DelegateLastClassLoader;
 import dalvik.system.PathClassLoader;
 import dalvik.system.VMRuntime;
@@ -36,8 +39,13 @@
 import java.util.Map;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.quality.Strictness;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -45,6 +53,12 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
 import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
@@ -56,6 +70,12 @@
     private static final String DELEGATE_LAST_CLASS_LOADER_NAME =
             DelegateLastClassLoader.class.getName();
 
+    @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+    @Mock Installer mInstaller;
+    @Mock IPackageManager mPM;
+    private final Object mInstallLock = new Object();
+    @Mock DexManager.Listener mListener;
+
     private DexManager mDexManager;
 
     private TestData mFooUser0;
@@ -90,7 +110,8 @@
         mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0,
                 DELEGATE_LAST_CLASS_LOADER_NAME);
 
-        mDexManager = new DexManager(null, null, null, null);
+        mDexManager = new DexManager(
+            mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock, mListener);
 
         // Foo and Bar are available to user0.
         // Only Bar is available to user1;
@@ -440,6 +461,20 @@
 
     }
 
+    @Test
+    public void testReconcileSecondaryDexFiles_invokesListener() throws Exception {
+        List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs();
+        notifyDexLoad(mFooUser0, fooSecondaries, mUser0);
+
+        when(mPM.getPackageInfo(mFooUser0.getPackageName(), 0, 0))
+                .thenReturn(mFooUser0.mPackageInfo);
+
+        mDexManager.reconcileSecondaryDexFiles(mFooUser0.getPackageName());
+
+        verify(mListener, times(fooSecondaries.size()))
+                .onReconcileSecondaryDexFile(any(ApplicationInfo.class),
+                        any(DexUseInfo.class), anyString(), anyInt());
+    }
 
     private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
             List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
@@ -492,12 +527,12 @@
     }
 
     private PackageUseInfo getPackageUseInfo(TestData testData) {
-        assertTrue(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
-        return mDexManager.getPackageUseInfoOrDefault(testData.mPackageInfo.packageName);
+        assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
+        return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
     }
 
     private void assertNoUseInfo(TestData testData) {
-        assertFalse(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName));
+        assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
     }
 
     private static PackageInfo getMockPackageInfo(String packageName, int userId) {
@@ -555,8 +590,8 @@
 
         List<String> getSecondaryDexPathsFromProtectedDirs() {
             List<String> paths = new ArrayList<>();
-            paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary6.dex");
-            paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary7.dex");
+            paths.add(mPackageInfo.applicationInfo.deviceProtectedDataDir + "/secondary6.dex");
+            paths.add(mPackageInfo.applicationInfo.credentialProtectedDataDir + "/secondary7.dex");
             return paths;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index c2072df..f559986a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -53,6 +53,7 @@
         assertFalse(opt.isDowngrade());
         assertFalse(opt.isForce());
         assertFalse(opt.isDexoptIdleBackgroundJob());
+        assertFalse(opt.isDexoptInstallWithDexMetadata());
     }
 
     @Test
@@ -65,7 +66,8 @@
                 DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
                 DexoptOptions.DEXOPT_DOWNGRADE  |
                 DexoptOptions.DEXOPT_AS_SHARED_LIBRARY |
-                DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+                DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB |
+                DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE;
 
         DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags);
         assertEquals(mPackageName, opt.getPackageName());
@@ -79,6 +81,7 @@
         assertTrue(opt.isForce());
         assertTrue(opt.isDexoptAsSharedLibrary());
         assertTrue(opt.isDexoptIdleBackgroundJob());
+        assertTrue(opt.isDexoptInstallWithDexMetadata());
     }
 
     @Test
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 7e08f51..da70f0f 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -47,6 +47,7 @@
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.ArtManager;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
@@ -1184,4 +1185,12 @@
             @Nullable DexModuleRegisterCallback callback) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public ArtManager getArtManager() {
+        throw new UnsupportedOperationException();
+    }
 }