Merge "Fixed name of autofill service on save UI." into pi-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index e54d2f6..20f0ba8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3663,6 +3663,7 @@
   }
 
   public final class ConfigUpdate {
+    field public static final java.lang.String ACTION_UPDATE_CARRIER_ID_DB = "android.os.action.UPDATE_CARRIER_ID_DB";
     field public static final java.lang.String ACTION_UPDATE_CARRIER_PROVISIONING_URLS = "android.intent.action.UPDATE_CARRIER_PROVISIONING_URLS";
     field public static final java.lang.String ACTION_UPDATE_CT_LOGS = "android.intent.action.UPDATE_CT_LOGS";
     field public static final java.lang.String ACTION_UPDATE_INTENT_FIREWALL = "android.intent.action.UPDATE_INTENT_FIREWALL";
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 4b84ed4..ca3257f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -736,7 +736,13 @@
         }
 
         if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
-            ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
+            // Temporarily disable logging of disk reads on the Looper thread as this is necessary
+            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+            try {
+                ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
+            } finally {
+                StrictMode.setThreadPolicy(oldPolicy);
+            }
         }
 
         if (addedPaths != null && addedPaths.size() > 0) {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 1b80d3d5..9154ce0 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1660,23 +1660,29 @@
      * @see ShutterCallback
      */
     public final boolean enableShutterSound(boolean enabled) {
-        if (!enabled) {
-            IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
-            IAudioService audioService = IAudioService.Stub.asInterface(b);
-            try {
-                if (audioService.isCameraSoundForced()) return false;
-            } catch (RemoteException e) {
-                Log.e(TAG, "Audio service is unavailable for queries");
+        boolean canDisableShutterSound = true;
+        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+        IAudioService audioService = IAudioService.Stub.asInterface(b);
+        try {
+            if (audioService.isCameraSoundForced()) {
+                canDisableShutterSound = false;
             }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Audio service is unavailable for queries");
+        }
+        if (!enabled && !canDisableShutterSound) {
+            return false;
         }
         synchronized (mShutterSoundLock) {
-            if (enabled && mHasAppOpsPlayAudio) {
-                Log.i(TAG, "Shutter sound is not allowed by AppOpsManager");
-                return false;
-            }
+            mShutterSoundEnabledFromApp = enabled;
+            // Return the result of _enableShutterSound(enabled) in all cases.
+            // If the shutter sound can be disabled, disable it when the device is in DnD mode.
             boolean ret = _enableShutterSound(enabled);
-            if (ret) {
-                mShutterSoundEnabledFromApp = enabled;
+            if (enabled && !mHasAppOpsPlayAudio) {
+                Log.i(TAG, "Shutter sound is not allowed by AppOpsManager");
+                if (canDisableShutterSound) {
+                    _enableShutterSound(false);
+                }
             }
             return ret;
         }
@@ -1739,9 +1745,18 @@
             }
             if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
                 if (!mHasAppOpsPlayAudio) {
+                    IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+                    IAudioService audioService = IAudioService.Stub.asInterface(b);
+                    try {
+                        if (audioService.isCameraSoundForced()) {
+                            return;
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Audio service is unavailable for queries");
+                    }
                     _enableShutterSound(false);
                 } else {
-                    _enableShutterSound(mShutterSoundEnabledFromApp);
+                    enableShutterSound(mShutterSoundEnabledFromApp);
                 }
             }
         }
diff --git a/core/java/android/os/ConfigUpdate.java b/core/java/android/os/ConfigUpdate.java
index dda0ed8..53b1c51 100644
--- a/core/java/android/os/ConfigUpdate.java
+++ b/core/java/android/os/ConfigUpdate.java
@@ -90,6 +90,14 @@
     public static final String ACTION_UPDATE_NETWORK_WATCHLIST
             = "android.intent.action.UPDATE_NETWORK_WATCHLIST";
 
+    /**
+     * Update carrier id config file.
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UPDATE_CARRIER_ID_DB
+            = "android.os.action.UPDATE_CARRIER_ID_DB";
+
     private ConfigUpdate() {
     }
 }
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f8dd7ac..9da3b21 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -747,6 +747,12 @@
                        jittransitionweightOptBuf,
                        "-Xjittransitionweight:");
 
+    property_get("dalvik.vm.profilebootimage", propBuf, "");
+    if (strcmp(propBuf, "true") == 0) {
+        addOption("-Xps-profile-boot-class-path");
+        addOption("-Xps-profile-aot-code");
+    }
+
     /*
      * Madvise related options.
      */
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 3ea6049..df735ae 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -139,18 +139,9 @@
         return throw_exception(env, ImageDecoder::kSourceMalformedData, "Could not open file",
                                nullptr, source);
     }
+
     std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file));
-
-    if (::lseek(descriptor, 0, SEEK_CUR) == 0) {
-        return native_create(env, std::move(fileStream), source);
-    }
-
-    // FIXME: This allows us to pretend the current location is the beginning,
-    // but it would be better if SkFILEStream allowed treating its starting
-    // point as the beginning.
-    std::unique_ptr<SkStream> stream(SkFrontBufferedStream::Make(std::move(fileStream),
-                SkCodec::MinBufferedBytesNeeded()));
-    return native_create(env, std::move(stream), source);
+    return native_create(env, std::move(fileStream), source);
 }
 
 static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1f8d43c..87d8915 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4262,7 +4262,7 @@
         <receiver android:name="com.android.server.updates.CarrierIdInstallReceiver"
                   android:permission="android.permission.UPDATE_CONFIG">
             <intent-filter>
-                <action android:name="com.android.internal.intent.action.UPDATE_CARRIER_ID_DB" />
+                <action android:name="android.os.action.UPDATE_CARRIER_ID_DB" />
                 <data android:scheme="content" android:host="*" android:mimeType="*/*" />
             </intent-filter>
         </receiver>
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index a265a5e..0ca0a11 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -74,6 +74,7 @@
 
     private boolean mEnforceBouncer = false;
     private boolean mBouncerOn = false;
+    private boolean mBouncerOffOnDown = false;
     private boolean mSessionActive = false;
     private boolean mIsTouchScreen = true;
     private int mState = StatusBarState.SHADE;
@@ -459,10 +460,19 @@
     public void onTouchEvent(MotionEvent event, int width, int height) {
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
             mIsTouchScreen = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
+            // If the bouncer was not shown during the down event,
+            // we want the entire gesture going to HumanInteractionClassifier
+            mBouncerOffOnDown = !mBouncerOn;
         }
-        if (mSessionActive && !mBouncerOn) {
-            mDataCollector.onTouchEvent(event, width, height);
-            mHumanInteractionClassifier.onTouchEvent(event);
+        if (mSessionActive) {
+            if (!mBouncerOn) {
+                // In case bouncer is "visible", but onFullyShown has not yet been called,
+                // avoid adding the event to DataCollector
+                mDataCollector.onTouchEvent(event, width, height);
+            }
+            if (mBouncerOffOnDown) {
+                mHumanInteractionClassifier.onTouchEvent(event);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 20b8407..48090f2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22995,18 +22995,27 @@
         }
     }
 
-    private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
+    private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
             boolean doingAll, long now) {
         if (mAdjSeq == app.adjSeq) {
-            // This adjustment has already been computed.
-            return app.curRawAdj;
+            if (app.adjSeq == app.completedAdjSeq) {
+                // This adjustment has already been computed successfully.
+                return false;
+            } else {
+                // The process is being computed, so there is a cycle. We cannot
+                // rely on this process's state.
+                app.containsCycle = true;
+                return false;
+            }
         }
 
         if (app.thread == null) {
             app.adjSeq = mAdjSeq;
             app.curSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
             app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-            return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ);
+            app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ;
+            app.completedAdjSeq = app.adjSeq;
+            return false;
         }
 
         app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
@@ -23019,6 +23028,8 @@
         final int appUid = app.info.uid;
         final int logUid = mCurOomAdjUid;
 
+        int prevAppAdj = app.curAdj;
+
         if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
             // The max adjustment doesn't allow this app to be anything
             // below foreground, so it is not worth doing work for it.
@@ -23063,7 +23074,10 @@
                   app.curSchedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
               }
             }
-            return (app.curAdj=app.maxAdj);
+            app.curAdj = app.maxAdj;
+            app.completedAdjSeq = app.adjSeq;
+            // if curAdj is less than prevAppAdj, then this process was promoted
+            return app.curAdj < prevAppAdj;
         }
 
         app.systemNoUi = false;
@@ -23075,6 +23089,8 @@
         int adj;
         int schedGroup;
         int procState;
+        int cachedAdjSeq;
+
         boolean foregroundActivities = false;
         mTmpBroadcastQueue.clear();
         if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
@@ -23388,9 +23404,9 @@
         // there are applications dependent on our services or providers, but
         // this gives us a baseline and makes sure we don't get into an
         // infinite recursion.
-        app.adjSeq = mAdjSeq;
         app.curRawAdj = adj;
         app.hasStartedServices = false;
+        app.adjSeq = mAdjSeq;
 
         if (mBackupTarget != null && app == mBackupTarget.app) {
             // If possible we want to avoid killing apps while they're being backed up
@@ -23489,8 +23505,15 @@
 
                     if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
                         ProcessRecord client = cr.binding.client;
-                        int clientAdj = computeOomAdjLocked(client, cachedAdj,
-                                TOP_APP, doingAll, now);
+                        computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
+                        if (client.containsCycle) {
+                            // We've detected a cycle. We should ignore this connection and allow
+                            // this process to retry computeOomAdjLocked later in case a later-checked
+                            // connection from a client  would raise its priority legitimately.
+                            app.containsCycle = true;
+                            continue;
+                        }
+                        int clientAdj = client.curRawAdj;
                         int clientProcState = client.curProcState;
                         if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
                             // If the other app is cached for any reason, for purposes here
@@ -23709,7 +23732,15 @@
                     // Being our own client is not interesting.
                     continue;
                 }
-                int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
+                computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
+                if (client.containsCycle) {
+                    // We've detected a cycle. We should ignore this connection and allow
+                    // this process to retry computeOomAdjLocked later in case a later-checked
+                    // connection from a client  would raise its priority legitimately.
+                    app.containsCycle = true;
+                    continue;
+                }
+                int clientAdj = client.curRawAdj;
                 int clientProcState = client.curProcState;
                 if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
                     // If the other app is cached for any reason, for purposes here
@@ -23937,8 +23968,10 @@
         app.curSchedGroup = schedGroup;
         app.curProcState = procState;
         app.foregroundActivities = foregroundActivities;
+        app.completedAdjSeq = mAdjSeq;
 
-        return app.curRawAdj;
+        // if curAdj is less than prevAppAdj, then this process was promoted
+        return app.curAdj < prevAppAdj;
     }
 
     /**
@@ -24912,12 +24945,23 @@
         int nextCachedAdj = curCachedAdj+1;
         int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
         int nextEmptyAdj = curEmptyAdj+2;
+
+        boolean retryCycles = false;
+
+        // need to reset cycle state before calling computeOomAdjLocked because of service connections
+        for (int i=N-1; i>=0; i--) {
+            ProcessRecord app = mLruProcesses.get(i);
+            app.containsCycle = false;
+        }
         for (int i=N-1; i>=0; i--) {
             ProcessRecord app = mLruProcesses.get(i);
             if (!app.killedByAm && app.thread != null) {
                 app.procStateChanged = false;
                 computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
 
+                // if any app encountered a cycle, we need to perform an additional loop later
+                retryCycles |= app.containsCycle;
+
                 // If we haven't yet assigned the final cached adj
                 // to the process, do that now.
                 if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
@@ -24971,6 +25015,39 @@
                     }
                 }
 
+
+            }
+        }
+
+        // Cycle strategy:
+        // - Retry computing any process that has encountered a cycle.
+        // - Continue retrying until no process was promoted.
+        // - Iterate from least important to most important.
+        int cycleCount = 0;
+        while (retryCycles) {
+            cycleCount++;
+            retryCycles = false;
+
+            for (int i=0; i<N; i++) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+                    app.adjSeq--;
+                    app.completedAdjSeq--;
+                }
+            }
+
+            for (int i=0; i<N; i++) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+                    if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now)) {
+                        retryCycles = true;
+                    }
+                }
+            }
+        }
+        for (int i=N-1; i>=0; i--) {
+            ProcessRecord app = mLruProcesses.get(i);
+            if (!app.killedByAm && app.thread != null) {
                 applyOomAdjLocked(app, true, now, nowElapsed);
 
                 // Count the number of process types.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b7fde1d..caf52e3 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -149,6 +149,8 @@
     String waitingToKill;       // Process is waiting to be killed when in the bg, and reason
     Object forcingToImportant;  // Token that is forcing this process to be important
     int adjSeq;                 // Sequence id for identifying oom_adj assignment cycles
+    int completedAdjSeq;        // Sequence id for identifying oom_adj assignment cycles
+    boolean containsCycle;      // Whether this app has encountered a cycle in the most recent update
     int lruSeq;                 // Sequence id for identifying LRU update cycles
     CompatibilityInfo compat;   // last used compatibility mode
     IBinder.DeathRecipient deathRecipient; // Who is watching for the death.
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 030c915..213ec36 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -189,7 +189,7 @@
         mController = new ColorDisplayController(getContext(), mCurrentUser);
         mController.setListener(this);
 
-        setCoefficientMatrix(getContext(), DisplayTransformManager.isNativeModeEnabled());
+        setCoefficientMatrix(getContext(), DisplayTransformManager.needsLinearColorMatrix());
 
         // Prepare color transformation matrix.
         setMatrix(mController.getColorTemperature(), mMatrixNight);
@@ -293,7 +293,7 @@
             mColorMatrixAnimator.cancel();
         }
 
-        setCoefficientMatrix(getContext(), DisplayTransformManager.isColorModeNative(mode));
+        setCoefficientMatrix(getContext(), DisplayTransformManager.needsLinearColorMatrix(mode));
         setMatrix(mController.getColorTemperature(), mMatrixNight);
 
         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
@@ -306,13 +306,12 @@
     }
 
     /**
-     * Set coefficients based on native mode. Use DisplayTransformManager#isNativeModeEnabled while
-     * setting is stable; when setting is changing, pass native mode selection directly.
+     * Set coefficients based on whether the color matrix is linear or not.
      */
-    private void setCoefficientMatrix(Context context, boolean isNative) {
-        final String[] coefficients = context.getResources().getStringArray(isNative
-                ? R.array.config_nightDisplayColorTemperatureCoefficientsNative
-                : R.array.config_nightDisplayColorTemperatureCoefficients);
+    private void setCoefficientMatrix(Context context, boolean needsLinear) {
+        final String[] coefficients = context.getResources().getStringArray(needsLinear
+                ? R.array.config_nightDisplayColorTemperatureCoefficients
+                : R.array.config_nightDisplayColorTemperatureCoefficientsNative);
         for (int i = 0; i < 9 && i < coefficients.length; i++) {
             mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
         }
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index 57d88e1..57a4f0d 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -231,21 +231,19 @@
     }
 
     /**
-     * Return true when colors are stretched from the working color space to the
-     * native color space.
+     * Return true when the color matrix works in linear space.
      */
-    public static boolean isNativeModeEnabled() {
+    public static boolean needsLinearColorMatrix() {
         return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR,
-                DISPLAY_COLOR_MANAGED) != DISPLAY_COLOR_MANAGED;
+                DISPLAY_COLOR_UNMANAGED) != DISPLAY_COLOR_UNMANAGED;
     }
 
     /**
-     * Return true when the specified colorMode stretches colors from the
-     * working color space to the native color space.
+     * Return true when the specified colorMode requires the color matrix to
+     * work in linear space.
      */
-    public static boolean isColorModeNative(int colorMode) {
-        return !(colorMode == ColorDisplayController.COLOR_MODE_NATURAL ||
-                 colorMode == ColorDisplayController.COLOR_MODE_BOOSTED);
+    public static boolean needsLinearColorMatrix(int colorMode) {
+        return colorMode != ColorDisplayController.COLOR_MODE_SATURATED;
     }
 
     public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a92fbb6..01f84c4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -25,6 +25,7 @@
 import android.accounts.IAccountManager;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.Application;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IIntentReceiver;
@@ -53,17 +54,20 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
+import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
-import android.os.BaseBundle;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IUserManager;
 import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
@@ -78,27 +82,41 @@
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
-
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
-
 import dalvik.system.DexFile;
-
-import libcore.io.IoUtils;
-
-import java.io.FileDescriptor;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
-import java.util.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.FileAttribute;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
 
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
+    /** Path where ART profiles snapshots are dumped for the shell user */
+    private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
 
     final IPackageManager mInterface;
     final private WeakHashMap<String, Resources> mResourceCache =
@@ -167,6 +185,8 @@
                     return runDexoptJob();
                 case "dump-profiles":
                     return runDumpProfiles();
+                case "snapshot-profile":
+                    return runSnapshotProfile();
                 case "uninstall":
                     return runUninstall();
                 case "clear":
@@ -1287,6 +1307,120 @@
         return 0;
     }
 
+    private int runSnapshotProfile() throws RemoteException {
+        PrintWriter pw = getOutPrintWriter();
+
+        // Parse the arguments
+        final String packageName = getNextArg();
+        final boolean isBootImage = "android".equals(packageName);
+
+        String codePath = null;
+        String opt;
+        while ((opt = getNextArg()) != null) {
+            switch (opt) {
+                case "--code-path":
+                    if (isBootImage) {
+                        pw.write("--code-path cannot be used for the boot image.");
+                        return -1;
+                    }
+                    codePath = getNextArg();
+                    break;
+                default:
+                    pw.write("Unknown arg: " + opt);
+                    return -1;
+            }
+        }
+
+        // If no code path was explicitly requested, select the base code path.
+        String baseCodePath = null;
+        if (!isBootImage) {
+            PackageInfo packageInfo = mInterface.getPackageInfo(packageName, /* flags */ 0,
+                    /* userId */0);
+            if (packageInfo == null) {
+                pw.write("Package not found " + packageName);
+                return -1;
+            }
+            baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+            if (codePath == null) {
+                codePath = baseCodePath;
+            }
+        }
+
+        // Create the profile snapshot.
+        final SnapshotRuntimeProfileCallback callback = new SnapshotRuntimeProfileCallback();
+        // The calling package is needed to debug permission access.
+        final String callingPackage = (Binder.getCallingUid() == Process.ROOT_UID)
+                ? "root" : "com.android.shell";
+        final int profileType = isBootImage
+                ? ArtManager.PROFILE_BOOT_IMAGE : ArtManager.PROFILE_APPS;
+        if (!mInterface.getArtManager().isRuntimeProfilingEnabled(profileType, callingPackage)) {
+            pw.println("Error: Runtime profiling is not enabled");
+            return -1;
+        }
+        mInterface.getArtManager().snapshotRuntimeProfile(profileType, packageName,
+                codePath, callback, callingPackage);
+        if (!callback.waitTillDone()) {
+            pw.println("Error: callback not called");
+            return callback.mErrCode;
+        }
+
+        // Copy the snapshot profile to the output profile file.
+        try (InputStream inStream = new AutoCloseInputStream(callback.mProfileReadFd)) {
+            final String outputFileSuffix = isBootImage || Objects.equals(baseCodePath, codePath)
+                    ? "" : ("-" + new File(codePath).getName());
+            final String outputProfilePath =
+                    ART_PROFILE_SNAPSHOT_DEBUG_LOCATION + packageName + outputFileSuffix + ".prof";
+            try (OutputStream outStream = new FileOutputStream(outputProfilePath)) {
+                Streams.copy(inStream, outStream);
+            }
+        } catch (IOException e) {
+            pw.println("Error when reading the profile fd: " + e.getMessage());
+            e.printStackTrace(pw);
+            return -1;
+        }
+        return 0;
+    }
+
+    private static class SnapshotRuntimeProfileCallback
+            extends ISnapshotRuntimeProfileCallback.Stub {
+        private boolean mSuccess = false;
+        private int mErrCode = -1;
+        private ParcelFileDescriptor mProfileReadFd = null;
+        private CountDownLatch mDoneSignal = new CountDownLatch(1);
+
+        @Override
+        public void onSuccess(ParcelFileDescriptor profileReadFd) {
+            mSuccess = true;
+            try {
+                // We need to dup the descriptor. We are in the same process as system server
+                // and we will be receiving the same object (which will be closed on the
+                // server side).
+                mProfileReadFd = profileReadFd.dup();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            mDoneSignal.countDown();
+        }
+
+        @Override
+        public void onError(int errCode) {
+            mSuccess = false;
+            mErrCode = errCode;
+            mDoneSignal.countDown();
+        }
+
+        boolean waitTillDone() {
+            boolean done = false;
+            try {
+                // The time-out is an arbitrary large value. Since this is a local call the result
+                // will come very fast.
+                done = mDoneSignal.await(10000000, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException ignored) {
+            }
+            return done && mSuccess;
+        }
+    }
+
     private int runUninstall() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         int flags = 0;
@@ -2761,7 +2895,13 @@
         pw.println("");
         pw.println("  dump-profiles TARGET-PACKAGE");
         pw.println("    Dumps method/class profile files to");
-        pw.println("    /data/misc/profman/TARGET-PACKAGE.txt");
+        pw.println("    " + ART_PROFILE_SNAPSHOT_DEBUG_LOCATION + "TARGET-PACKAGE.txt");
+        pw.println("");
+        pw.println("  snapshot-profile TARGET-PACKAGE [--code-path path]");
+        pw.println("    Take a snapshot of the package profiles to");
+        pw.println("    " + ART_PROFILE_SNAPSHOT_DEBUG_LOCATION
+                + "TARGET-PACKAGE[-code-path].prof");
+        pw.println("    If TARGET-PACKAGE=android it will take a snapshot of the boot image");
         pw.println("");
         pw.println("  set-home-activity [--user USER_ID] TARGET-COMPONENT");
         pw.println("    Set the default home activity (aka launcher).");
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
index a14b950..b4b34c5 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java
@@ -40,6 +40,7 @@
 import android.support.test.filters.SmallTest;
 import android.testing.DexmakerShareClassLoaderRule;
 
+import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.server.LocalServices;
 import com.android.server.pm.PackageManagerService;
@@ -150,6 +151,28 @@
     }
 
     @Test
+    public void testSuspendedPackage() {
+        mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
+        final String suspendingPackage = "com.test.suspending.package";
+        final String dialogMessage = "Test Message";
+        when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
+                .thenReturn(suspendingPackage);
+        when(mPackageManagerInternal.getSuspendedDialogMessage(TEST_PACKAGE_NAME, TEST_USER_ID))
+                .thenReturn(dialogMessage);
+        // THEN calling intercept returns true
+        assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+        // Check intent parameters
+        assertEquals(dialogMessage,
+                mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_DIALOG_MESSAGE));
+        assertEquals(suspendingPackage,
+                mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDING_PACKAGE));
+        assertEquals(TEST_PACKAGE_NAME,
+                mInterceptor.mIntent.getStringExtra(SuspendedAppActivity.EXTRA_SUSPENDED_PACKAGE));
+        assertEquals(TEST_USER_ID, mInterceptor.mIntent.getIntExtra(Intent.EXTRA_USER_ID, -1000));
+    }
+
+    @Test
     public void testInterceptQuietProfile() {
         // GIVEN that the user the activity is starting as is currently in quiet mode
         when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);