Add SystemApis to expose runtime profile information

The API allows a system apps which acquired
{@code android.permission.READ_RUNTIME_PROFILE} to snapshot the runtime
profiles of installed packages.

The API is implemented in a new service class (AndroidRuntimeManager)
accessible from the context using
context().getPackageManager().getAndroidRuntimeManager().

The main functionality is exposed as a one way call into the
AndroidRuntimeManager with the result being posted on a callback. The
profile is available to the caller as a read-only ParcelFileDescriptor.

This CL only adds the API interfaces and validation. It does not fully
implement the functionality.

oneway void snapshotRuntimeProfile(in String packageName,
  in String codePath, in ISnapshotRuntimeProfileCallback callback)

Bug: 30934496
Test: gts-tradefed -m GtsAndroidRuntimeManagerHostTestCases

Change-Id: Iaa6be4715840f24508acba3162ea9c1ab725bd38
diff --git a/Android.bp b/Android.bp
index 32adc1c..c6c966c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -134,6 +134,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 16404e4..abb789d 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";
@@ -843,6 +844,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);
@@ -950,6 +952,24 @@
 
 }
 
+package android.content.pm.dex {
+
+  public class ArtManager {
+    method public boolean isRuntimeProfilingEnabled();
+    method public void snapshotRuntimeProfile(java.lang.String, java.lang.String, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback, android.os.Handler);
+    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 005b7c3..1dbdb59 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -55,6 +55,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;
@@ -121,6 +122,8 @@
     private UserManager mUserManager;
     @GuardedBy("mLock")
     private PackageInstaller mInstaller;
+    @GuardedBy("mLock")
+    private ArtManager mArtManager;
 
     @GuardedBy("mDelegates")
     private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@@ -2750,4 +2753,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/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 64d33d5..56a0def 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;
@@ -657,4 +658,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 f796aab..a5cc902 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;
@@ -5842,4 +5843,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/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java
new file mode 100644
index 0000000..201cd8d
--- /dev/null
+++ b/core/java/android/content/pm/dex/ArtManager.java
@@ -0,0 +1,156 @@
+/**
+ * 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.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * 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;
+
+    private IArtManager mArtManager;
+
+    /**
+     * @hide
+     */
+    public ArtManager(@NonNull IArtManager manager) {
+        mArtManager = manager;
+    }
+
+    /**
+     * Snapshots the runtime profile for an apk belonging to the package {@code packageName}.
+     * The apk is identified by {@code codePath}. The calling process must have
+     * {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     *
+     * The result will be posted on {@code handler} using the given {@code callback}.
+     * The profile being available as a read-only {@link android.os.ParcelFileDescriptor}.
+     *
+     * @param packageName the target package name
+     * @param codePath the code path for which the profile should be retrieved
+     * @param callback the callback which should be used for the result
+     * @param handler the handler which should be used to post the result
+     */
+    @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
+    public void snapshotRuntimeProfile(@NonNull String packageName, @NonNull String codePath,
+            @NonNull SnapshotRuntimeProfileCallback callback, @NonNull Handler handler) {
+        Slog.d(TAG, "Requesting profile snapshot for " + packageName + ":" + codePath);
+
+        SnapshotRuntimeProfileCallbackDelegate delegate =
+                new SnapshotRuntimeProfileCallbackDelegate(callback, handler.getLooper());
+        try {
+            mArtManager.snapshotRuntimeProfile(packageName, codePath, delegate);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Returns true if runtime profiles are enabled, false otherwise.
+     *
+     * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
+    public boolean isRuntimeProfilingEnabled() {
+        try {
+            return mArtManager.isRuntimeProfilingEnabled();
+        } 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
+            implements Handler.Callback {
+        private static final int MSG_SNAPSHOT_OK = 1;
+        private static final int MSG_ERROR = 2;
+        private final ArtManager.SnapshotRuntimeProfileCallback mCallback;
+        private final Handler mHandler;
+
+        private SnapshotRuntimeProfileCallbackDelegate(
+                ArtManager.SnapshotRuntimeProfileCallback callback, Looper looper) {
+            mCallback = callback;
+            mHandler = new Handler(looper, this);
+        }
+
+        @Override
+        public void onSuccess(ParcelFileDescriptor profileReadFd) {
+            mHandler.obtainMessage(MSG_SNAPSHOT_OK, profileReadFd).sendToTarget();
+        }
+
+        @Override
+        public void onError(int errCode) {
+            mHandler.obtainMessage(MSG_ERROR, errCode, 0).sendToTarget();
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SNAPSHOT_OK:
+                    mCallback.onSuccess((ParcelFileDescriptor) msg.obj);
+                    break;
+                case MSG_ERROR:
+                    mCallback.onError(msg.arg1);
+                    break;
+                default: return false;
+            }
+            return true;
+        }
+    }
+}
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..8cbb452
--- /dev/null
+++ b/core/java/android/content/pm/dex/IArtManager.aidl
@@ -0,0 +1,44 @@
+/*
+** 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 the runtime profile for an apk belonging to the package {@param packageName}.
+     * The apk is identified by {@param codePath}. The calling process must have
+     * {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     *
+     * The result will be posted on {@param callback} with the profile being available as a
+     * read-only {@link android.os.ParcelFileDescriptor}.
+     */
+    oneway void snapshotRuntimeProfile(in String packageName,
+        in String codePath, in ISnapshotRuntimeProfileCallback callback);
+
+    /**
+     * Returns true if runtime profiles are enabled, false otherwise.
+     *
+     * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+     */
+    boolean isRuntimeProfilingEnabled();
+}
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d9fb230..0920426 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3616,6 +3616,11 @@
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
         android:protectionLevel="signature" />
 
+    <!-- @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/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4b6589c..440ab13e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -111,7 +111,6 @@
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
 
 import android.Manifest;
 import android.annotation.IntDef;
@@ -187,6 +186,7 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VerifierInfo;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.IArtManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -265,14 +265,12 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.IParcelFileDescriptorFactory;
-import com.android.internal.os.RoSystemProperties;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.Zygote;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -292,6 +290,7 @@
 import com.android.server.pm.Installer.InstallerException;
 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;
@@ -307,30 +306,23 @@
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
-import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
 
 import libcore.io.IoUtils;
-import libcore.io.Streams;
-import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FileReader;
 import java.io.FilenameFilter;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -341,15 +333,12 @@
 import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -363,7 +352,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.zip.GZIPInputStream;
 
 /**
  * Keep track of all those APKs everywhere.
@@ -939,6 +927,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).
@@ -3047,6 +3037,7 @@
             }
 
             mInstallerService = new PackageInstallerService(context, this);
+            mArtManagerService = new ArtManagerService(this);
             final Pair<ComponentName, String> instantAppResolverComponent =
                     getInstantAppResolverLPr();
             if (instantAppResolverComponent != null) {
@@ -22273,6 +22264,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/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
new file mode 100644
index 0000000..5a1f840
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -0,0 +1,155 @@
+/*
+ * 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.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.dex.ArtManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.content.pm.IPackageManager;
+import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.Preconditions;
+
+/**
+ * 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;
+
+    private final IPackageManager mPackageManager;
+    private final Handler mHandler;
+
+    public ArtManagerService(IPackageManager pm) {
+        mPackageManager = pm;
+        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
+    }
+
+    @Override
+    public void snapshotRuntimeProfile(String packageName, String codePath,
+            ISnapshotRuntimeProfileCallback callback) {
+        // Sanity checks on the arguments.
+        Preconditions.checkStringNotEmpty(packageName);
+        Preconditions.checkStringNotEmpty(codePath);
+        Preconditions.checkNotNull(callback);
+
+        // Verify that the caller has the right permissions.
+        checkReadRuntimeProfilePermission();
+
+        if (DEBUG) {
+            Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
+        }
+
+        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[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
+        if (!pathFound && (splitCodePaths != null)) {
+            for (String path : splitCodePaths) {
+                if (path.equals(codePath)) {
+                    pathFound = true;
+                    break;
+                }
+            }
+        }
+        if (!pathFound) {
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
+            return;
+        }
+
+        // All good, move forward and get the profile.
+        postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+    }
+
+    @Override
+    public boolean isRuntimeProfilingEnabled() {
+        // Verify that the caller has the right permissions.
+        checkReadRuntimeProfilePermission();
+
+        return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+    }
+
+    /**
+     * Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
+     * on the internal {@code mHandler}.
+     */
+    private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
+            int errCode) {
+        mHandler.post(() -> {
+            try {
+                callback.onError(errCode);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to callback 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.
+        }
+    }
+}
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 0c562e6..ce8019f 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -46,6 +46,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;
@@ -1174,4 +1175,12 @@
             @Nullable DexModuleRegisterCallback callback) {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public ArtManager getArtManager() {
+        throw new UnsupportedOperationException();
+    }
 }