Merge "Remove leftover DO/PO check in isPackageSuspended"
diff --git a/Android.mk b/Android.mk
index 03b2533..b46aeb8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -568,6 +568,7 @@
     android.hardware.thermal@1.0-java-constants         \
     android.hardware.health@1.0-java-constants          \
     android.hardware.usb@1.0-java-constants             \
+    android.hardware.vibrator@1.0-java-constants        \
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := stream
 LOCAL_PROTOC_FLAGS := \
diff --git a/api/current.txt b/api/current.txt
index e6bacc0..90c48fb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31655,13 +31655,25 @@
     field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createWaveform(long[], int);
+    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+    method public int describeContents();
+    field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+    field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+  }
+
   public abstract class Vibrator {
     method public abstract void cancel();
+    method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
-    method public void vibrate(long);
-    method public void vibrate(long, android.media.AudioAttributes);
-    method public void vibrate(long[], int);
-    method public void vibrate(long[], int, android.media.AudioAttributes);
+    method public deprecated void vibrate(long);
+    method public deprecated void vibrate(long, android.media.AudioAttributes);
+    method public deprecated void vibrate(long[], int);
+    method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+    method public void vibrate(android.os.VibrationEffect);
+    method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 173aaf3..0d54bee 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34493,13 +34493,25 @@
   public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation {
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createWaveform(long[], int);
+    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+    method public int describeContents();
+    field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+    field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+  }
+
   public abstract class Vibrator {
     method public abstract void cancel();
+    method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
-    method public void vibrate(long);
-    method public void vibrate(long, android.media.AudioAttributes);
-    method public void vibrate(long[], int);
-    method public void vibrate(long[], int, android.media.AudioAttributes);
+    method public deprecated void vibrate(long);
+    method public deprecated void vibrate(long, android.media.AudioAttributes);
+    method public deprecated void vibrate(long[], int);
+    method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+    method public void vibrate(android.os.VibrationEffect);
+    method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 99d960b..e56255c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31780,13 +31780,25 @@
     field public static final int USER_CREATION_FAILED_NO_MORE_USERS = 2; // 0x2
   }
 
+  public abstract class VibrationEffect implements android.os.Parcelable {
+    method public static android.os.VibrationEffect createOneShot(long, int);
+    method public static android.os.VibrationEffect createWaveform(long[], int);
+    method public static android.os.VibrationEffect createWaveform(long[], int[], int);
+    method public int describeContents();
+    field public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
+    field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
+  }
+
   public abstract class Vibrator {
     method public abstract void cancel();
+    method public abstract boolean hasAmplitudeControl();
     method public abstract boolean hasVibrator();
-    method public void vibrate(long);
-    method public void vibrate(long, android.media.AudioAttributes);
-    method public void vibrate(long[], int);
-    method public void vibrate(long[], int, android.media.AudioAttributes);
+    method public deprecated void vibrate(long);
+    method public deprecated void vibrate(long, android.media.AudioAttributes);
+    method public deprecated void vibrate(long[], int);
+    method public deprecated void vibrate(long[], int, android.media.AudioAttributes);
+    method public void vibrate(android.os.VibrationEffect);
+    method public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
   }
 
   public class WorkSource implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8a0af9a..9f2f669 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2177,31 +2177,62 @@
         private final GraphicBuffer mSnapshot;
         private final int mOrientation;
         private final Rect mContentInsets;
+        private final boolean mReducedResolution;
+        private final float mScale;
 
-        public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets) {
+        public TaskSnapshot(GraphicBuffer snapshot, int orientation, Rect contentInsets,
+                boolean reducedResolution, float scale) {
             mSnapshot = snapshot;
             mOrientation = orientation;
             mContentInsets = new Rect(contentInsets);
+            mReducedResolution = reducedResolution;
+            mScale = scale;
         }
 
         private TaskSnapshot(Parcel source) {
             mSnapshot = source.readParcelable(null /* classLoader */);
             mOrientation = source.readInt();
             mContentInsets = source.readParcelable(null /* classLoader */);
+            mReducedResolution = source.readBoolean();
+            mScale = source.readFloat();
         }
 
+        /**
+         * @return The graphic buffer representing the screenshot.
+         */
         public GraphicBuffer getSnapshot() {
             return mSnapshot;
         }
 
+        /**
+         * @return The screen orientation the screenshot was taken in.
+         */
         public int getOrientation() {
             return mOrientation;
         }
 
+        /**
+         * @return The system/content insets on the snapshot. These can be clipped off in order to
+         *         remove any areas behind system bars in the snapshot.
+         */
         public Rect getContentInsets() {
             return mContentInsets;
         }
 
+        /**
+         * @return Whether this snapshot is a down-sampled version of the full resolution.
+         */
+        public boolean isReducedResolution() {
+            return mReducedResolution;
+        }
+
+        /**
+         * @return The scale this snapshot was taken in.
+         */
+        public float getScale() {
+            return mScale;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -2212,12 +2243,15 @@
             dest.writeParcelable(mSnapshot, 0);
             dest.writeInt(mOrientation);
             dest.writeParcelable(mContentInsets, 0);
+            dest.writeBoolean(mReducedResolution);
+            dest.writeFloat(mScale);
         }
 
         @Override
         public String toString() {
             return "TaskSnapshot{mSnapshot=" + mSnapshot + " mOrientation=" + mOrientation
-                    + " mContentInsets=" + mContentInsets.toShortString();
+                    + " mContentInsets=" + mContentInsets.toShortString()
+                    + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale;
         }
 
         public static final Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index c854667..d940857 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -600,9 +600,12 @@
     void cancelTaskThumbnailTransition(int taskId);
 
     /**
+     * @param taskId the id of the task to retrieve the snapshots for
+     * @param reducedResolution if set, if the snapshot needs to be loaded from disk, this will load
+     *                          a reduced resolution of it, which is much faster
      * @return a graphic buffer representing a screenshot of a task
      */
-    ActivityManager.TaskSnapshot getTaskSnapshot(int taskId);
+    ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution);
 
     void scheduleApplicationInfoChanged(in List<String> packageNames, int userId);
     void setPersistentVrThread(int tid);
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91b87d7..790a952 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -172,6 +172,25 @@
                 return new SecurityEvent[size];
             }
         };
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            SecurityEvent other = (SecurityEvent) o;
+            return mEvent.equals(other.mEvent);
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public int hashCode() {
+            return mEvent.hashCode();
+        }
     }
     /**
      * Retrieve all security logs and return immediately.
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 949d644..ccf30ac 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -74,8 +74,6 @@
     private static final boolean TRACE_FOR_PRELOAD = false;
     private static final boolean TRACE_FOR_MISS_PRELOAD = false;
 
-    private static final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigJavaToNative(
-            ActivityInfo.CONFIG_LAYOUT_DIRECTION);
 
     private static final int ID_OTHER = 0x01000004;
 
@@ -636,8 +634,8 @@
                 }
             } else {
                 if (verifyPreloadConfig(
-                        changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
-                    if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) {
+                        changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
+                    if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
                         // If this resource does not vary based on layout direction,
                         // we can put it in all of the preload maps.
                         sPreloadedDrawables[0].put(key, cs);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6e202b0..631b77d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -33,6 +33,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -1154,23 +1155,33 @@
             return true;
         }
 
-        /**
-         * @hide
-         */
         @Override
-        public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
-            vibrate(new long[] { 0, milliseconds}, -1);
+        public boolean hasAmplitudeControl() {
+            return false;
         }
 
         /**
          * @hide
          */
         @Override
-        public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-                AudioAttributes attributes) {
-            if (repeat >= pattern.length) {
-                throw new ArrayIndexOutOfBoundsException();
+        public void vibrate(int uid, String opPkg,
+                VibrationEffect effect, AudioAttributes attributes) {
+            long[] pattern;
+            int repeat;
+            if (effect instanceof VibrationEffect.OneShot) {
+                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
+                pattern = new long[] { 0, oneShot.getTiming() };
+                repeat = -1;
+            } else if (effect instanceof VibrationEffect.Waveform) {
+                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+                pattern = waveform.getTimings();
+                repeat = waveform.getRepeatIndex();
+            } else {
+                // TODO: Add support for prebaked effects
+                Log.w(TAG, "Pre-baked effects aren't supported on input devices");
+                return;
             }
+
             try {
                 mIm.vibrate(mDeviceId, pattern, repeat, mToken);
             } catch (RemoteException ex) {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6f2857d..e59c3ae 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,12 +16,14 @@
 
 package android.os;
 
+import android.os.VibrationEffect;
+
 /** {@hide} */
 interface IVibratorService
 {
     boolean hasVibrator();
-    void vibrate(int uid, String opPkg, long milliseconds, int usageHint, IBinder token);
-    void vibratePattern(int uid, String opPkg, in long[] pattern, int repeat, int usageHint, IBinder token);
+    boolean hasAmplitudeControl();
+    void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, IBinder token);
     void cancelVibrate(IBinder token);
 }
 
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 19b452f..b8bdc89 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -38,22 +38,14 @@
         return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+    public boolean hasAmplitudeControl() {
+        return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-            AudioAttributes attributes) {
-        if (repeat >= pattern.length) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
+    public void vibrate(int uid, String opPkg,
+            VibrationEffect effect, AudioAttributes attributes) {
     }
 
     @Override
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index c488811..f776c17 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -32,14 +32,12 @@
     private final Binder mToken = new Binder();
 
     public SystemVibrator() {
-        mService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
     }
 
     public SystemVibrator(Context context) {
         super(context);
-        mService = IVibratorService.Stub.asInterface(
-                ServiceManager.getService("vibrator"));
+        mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
     }
 
     @Override
@@ -55,47 +53,33 @@
         return false;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public void vibrate(int uid, String opPkg, long milliseconds, AudioAttributes attributes) {
+    public boolean hasAmplitudeControl() {
+        if (mService == null) {
+            Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
+            return false;
+        }
+        try {
+            return mService.hasAmplitudeControl();
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public void vibrate(int uid, String opPkg,
+            VibrationEffect effect, AudioAttributes attributes) {
         if (mService == null) {
             Log.w(TAG, "Failed to vibrate; no vibrator service.");
             return;
         }
         try {
-            mService.vibrate(uid, opPkg, milliseconds, usageForAttributes(attributes), mToken);
+            mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
     }
 
-    /**
-     * @hide
-     */
-    @Override
-    public void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-            AudioAttributes attributes) {
-        if (mService == null) {
-            Log.w(TAG, "Failed to vibrate; no vibrator service.");
-            return;
-        }
-        // catch this here because the server will do nothing.  pattern may
-        // not be null, let that be checked, because the server will drop it
-        // anyway
-        if (repeat < pattern.length) {
-            try {
-                mService.vibratePattern(uid, opPkg, pattern, repeat, usageForAttributes(attributes),
-                        mToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to vibrate.", e);
-            }
-        } else {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-    }
-
     private static int usageForAttributes(AudioAttributes attributes) {
         return attributes != null ? attributes.getUsage() : AudioAttributes.USAGE_UNKNOWN;
     }
diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl
new file mode 100644
index 0000000..dcc79d7
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.os;
+
+parcelable VibrationEffect;
+parcelable VibrationEffect.OneShotVibration;
+parcelable VibrationEffect.WaveformVibration;
+parcelable VibrationEffect.EffectVibration;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
new file mode 100644
index 0000000..eceaa31
--- /dev/null
+++ b/core/java/android/os/VibrationEffect.java
@@ -0,0 +1,444 @@
+/*
+ * 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 android.os;
+
+import android.hardware.vibrator.V1_0.Constants.Effect;
+
+import java.util.Arrays;
+
+/**
+ * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
+ *
+ * These effects may be any number of things, from single shot vibrations to complex waveforms.
+ */
+public abstract class VibrationEffect implements Parcelable {
+    private static final int PARCEL_TOKEN_ONE_SHOT = 1;
+    private static final int PARCEL_TOKEN_WAVEFORM = 2;
+    private static final int PARCEL_TOKEN_EFFECT = 3;
+
+    /**
+     * The default vibration strength of the device.
+     */
+    public static final int DEFAULT_AMPLITUDE = -1;
+
+    /**
+     * A click effect.
+     *
+     * @see #get(int)
+     * @hide
+     */
+    public static final int EFFECT_CLICK = Effect.CLICK;
+
+    /**
+     * A double click effect.
+     *
+     * @see #get(int)
+     * @hide
+     */
+    public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
+
+    /** @hide to prevent subclassing from outside of the framework */
+    public VibrationEffect() { }
+
+    /**
+     * Create a one shot vibration.
+     *
+     * One shot vibrations will vibrate constantly for the specified period of time at the
+     * specified amplitude, and then stop.
+     *
+     * @param milliseconds The number of milliseconds to vibrate. This must be a positive number.
+     * @param amplitude The strength of the vibration. This must be a value between 1 and 255, or
+     * {@link #DEFAULT_AMPLITUDE}.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createOneShot(long milliseconds, int amplitude) {
+        VibrationEffect effect = new OneShot(milliseconds, amplitude);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Create a waveform vibration.
+     *
+     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * each pair, the value in the amplitude array determines the strength of the vibration and the
+     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * <p>
+     * The amplitude array of the generated waveform will be the same size as the given
+     * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE},
+     * starting with 0. Therefore the first timing value will be the period to wait before turning
+     * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE}
+     * strength, etc.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the timings array at which to start the
+     * repetition, or -1 to disable repeating.
+     * </p>
+     *
+     * @param timings The pattern of alternating on-off timings, starting with off. Timing values
+     *                of 0 will cause the timing / amplitude pair to be ignored.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+     *               want to repeat.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createWaveform(long[] timings, int repeat) {
+        int[] amplitudes = new int[timings.length];
+        for (int i = 0; i < (timings.length / 2); i++) {
+            amplitudes[i*2 + 1] = VibrationEffect.DEFAULT_AMPLITUDE;
+        }
+        return createWaveform(timings, amplitudes, repeat);
+    }
+
+    /**
+     * Create a waveform vibration.
+     *
+     * Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For
+     * each pair, the value in the amplitude array determines the strength of the vibration and the
+     * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no
+     * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the timings array at which to start the
+     * repetition, or -1 to disable repeating.
+     * </p>
+     *
+     * @param timings The timing values of the timing / amplitude pairs. Timing values of 0
+     *                will cause the pair to be ignored.
+     * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values
+     *                   must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An
+     *                   amplitude value of 0 implies the motor is off.
+     * @param repeat The index into the timings array at which to repeat, or -1 if you you don't
+     *               want to repeat.
+     *
+     * @return The desired effect.
+     */
+    public static VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+        VibrationEffect effect = new Waveform(timings, amplitudes, repeat);
+        effect.validate();
+        return effect;
+    }
+
+    /**
+     * Get a predefined vibration effect.
+     *
+     * Predefined effects are a set of common vibration effects that should be identical, regardless
+     * of the app they come from, in order to provide a cohesive experience for users across
+     * the entire device. They also may be custom tailored to the device hardware in order to
+     * provide a better experience than you could otherwise build using the generic building
+     * blocks.
+     *
+     * @param effectId The ID of the effect to perform:
+     * {@link #EFFECT_CLICK}, {@link #EFFECT_DOUBLE_CLICK}.
+     *
+     * @return The desired effect.
+     * @hide
+     */
+    public static VibrationEffect get(int effectId) {
+        VibrationEffect effect = new Prebaked(effectId);
+        effect.validate();
+        return effect;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    public abstract void validate();
+
+    /** @hide */
+    public static class OneShot extends VibrationEffect implements Parcelable {
+        private long mTiming;
+        private int mAmplitude;
+
+        public OneShot(Parcel in) {
+            this(in.readLong(), in.readInt());
+        }
+
+        public OneShot(long milliseconds, int amplitude) {
+            mTiming = milliseconds;
+            mAmplitude = amplitude;
+        }
+
+        public long getTiming() {
+            return mTiming;
+        }
+
+        public int getAmplitude() {
+            return mAmplitude;
+        }
+
+        @Override
+        public void validate() {
+            if (mAmplitude < -1 || mAmplitude == 0 || mAmplitude > 255) {
+                throw new IllegalArgumentException(
+                        "amplitude must either be DEFAULT_AMPLITUDE, " +
+                        "or between 1 and 255 inclusive");
+            }
+            if (mTiming <= 0) {
+                throw new IllegalArgumentException("timing must be positive");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.OneShot)) {
+                return false;
+            }
+            VibrationEffect.OneShot other = (VibrationEffect.OneShot) o;
+            return other.mTiming == mTiming && other.mAmplitude == mAmplitude;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 37 * (int) mTiming;
+            result = 37 * mAmplitude;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "OneShot{mTiming=" + mTiming +", mAmplitude=" + mAmplitude + "}";
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_ONE_SHOT);
+            out.writeLong(mTiming);
+            out.writeInt(mAmplitude);
+        }
+
+        public static final Parcelable.Creator<OneShot> CREATOR =
+            new Parcelable.Creator<OneShot>() {
+                @Override
+                public OneShot createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new OneShot(in);
+                }
+                @Override
+                public OneShot[] newArray(int size) {
+                    return new OneShot[size];
+                }
+            };
+    }
+
+    /** @hide */
+    public static class Waveform extends VibrationEffect implements Parcelable {
+        private long[] mTimings;
+        private int[] mAmplitudes;
+        private int mRepeat;
+
+        public Waveform(Parcel in) {
+            this(in.createLongArray(), in.createIntArray(), in.readInt());
+        }
+
+        public Waveform(long[] timings, int[] amplitudes, int repeat) {
+            mTimings = new long[timings.length];
+            System.arraycopy(timings, 0, mTimings, 0, timings.length);
+            mAmplitudes = new int[amplitudes.length];
+            System.arraycopy(amplitudes, 0, mAmplitudes, 0, amplitudes.length);
+            mRepeat = repeat;
+        }
+
+        public long[] getTimings() {
+            return mTimings;
+        }
+
+        public int[] getAmplitudes() {
+            return mAmplitudes;
+        }
+
+        public int getRepeatIndex() {
+            return mRepeat;
+        }
+
+        @Override
+        public void validate() {
+            if (mTimings.length != mAmplitudes.length) {
+                throw new IllegalArgumentException(
+                        "timing and amplitude arrays must be of equal length");
+            }
+            if (!hasNonZeroEntry(mTimings)) {
+                throw new IllegalArgumentException("at least one timing must be non-zero");
+            }
+            for (long timing : mTimings) {
+                if (timing < 0) {
+                    throw new IllegalArgumentException("timings must all be >= 0");
+                }
+            }
+            for (int amplitude : mAmplitudes) {
+                if (amplitude < -1 || amplitude > 255) {
+                    throw new IllegalArgumentException(
+                            "amplitudes must all be DEFAULT_AMPLITUDE or between 0 and 255");
+                }
+            }
+            if (mRepeat < -1 || mRepeat >= mTimings.length) {
+                throw new IllegalArgumentException("repeat index must be >= -1");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.Waveform)) {
+                return false;
+            }
+            VibrationEffect.Waveform other = (VibrationEffect.Waveform) o;
+            return Arrays.equals(mTimings, other.mTimings) &&
+                Arrays.equals(mAmplitudes, other.mAmplitudes) &&
+                mRepeat == other.mRepeat;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 37 * Arrays.hashCode(mTimings);
+            result = 37 * Arrays.hashCode(mAmplitudes);
+            result = 37 * mRepeat;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "Waveform{mTimings=" + Arrays.toString(mTimings) +
+                ", mAmplitudes=" + Arrays.toString(mAmplitudes) +
+                ", mRepeat=" + mRepeat +
+                "}";
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_WAVEFORM);
+            out.writeLongArray(mTimings);
+            out.writeIntArray(mAmplitudes);
+            out.writeInt(mRepeat);
+        }
+
+        private static boolean hasNonZeroEntry(long[] vals) {
+            for (long val : vals) {
+                if (val != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+
+        public static final Parcelable.Creator<Waveform> CREATOR =
+            new Parcelable.Creator<Waveform>() {
+                @Override
+                public Waveform createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new Waveform(in);
+                }
+                @Override
+                public Waveform[] newArray(int size) {
+                    return new Waveform[size];
+                }
+            };
+    }
+
+    /** @hide */
+    public static class Prebaked extends VibrationEffect implements Parcelable {
+        private int mEffectId;
+
+        public Prebaked(Parcel in) {
+            this(in.readInt());
+        }
+
+        public Prebaked(int effectId) {
+            mEffectId = effectId;
+        }
+
+        public int getId() {
+            return mEffectId;
+        }
+
+        @Override
+        public void validate() {
+            if (mEffectId != EFFECT_CLICK) {
+                throw new IllegalArgumentException("Unknown prebaked effect type");
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof VibrationEffect.Prebaked)) {
+                return false;
+            }
+            VibrationEffect.Prebaked other = (VibrationEffect.Prebaked) o;
+            return mEffectId == other.mEffectId;
+        }
+
+        @Override
+        public int hashCode() {
+            return mEffectId;
+        }
+
+        @Override
+        public String toString() {
+            return "Prebaked{mEffectId=" + mEffectId + "}";
+        }
+
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_EFFECT);
+            out.writeInt(mEffectId);
+        }
+
+        public static final Parcelable.Creator<Prebaked> CREATOR =
+            new Parcelable.Creator<Prebaked>() {
+                @Override
+                public Prebaked createFromParcel(Parcel in) {
+                    // Skip the type token
+                    in.readInt();
+                    return new Prebaked(in);
+                }
+                @Override
+                public Prebaked[] newArray(int size) {
+                    return new Prebaked[size];
+                }
+            };
+    }
+
+    public static final Parcelable.Creator<VibrationEffect> CREATOR =
+            new Parcelable.Creator<VibrationEffect>() {
+                @Override
+                public VibrationEffect createFromParcel(Parcel in) {
+                    int token = in.readInt();
+                    if (token == PARCEL_TOKEN_ONE_SHOT) {
+                        return new OneShot(in);
+                    } else if (token == PARCEL_TOKEN_WAVEFORM) {
+                        return new Waveform(in);
+                    } else if (token == PARCEL_TOKEN_EFFECT) {
+                        return new Prebaked(in);
+                    } else {
+                        throw new IllegalStateException(
+                                "Unexpected vibration event type token in parcel.");
+                    }
+                }
+                @Override
+                public VibrationEffect[] newArray(int size) {
+                    return new VibrationEffect[size];
+                }
+            };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index f9b7666..b1f6421 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -55,12 +55,22 @@
     public abstract boolean hasVibrator();
 
     /**
+     * Check whether the vibrator has amplitude control.
+     *
+     * @return True if the hardware can control the amplitude of the vibrations, otherwise false.
+     */
+    public abstract boolean hasAmplitudeControl();
+
+    /**
      * Vibrate constantly for the specified period of time.
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#VIBRATE}.
      *
      * @param milliseconds The number of milliseconds to vibrate.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
      */
+    @Deprecated
     public void vibrate(long milliseconds) {
         vibrate(milliseconds, null);
     }
@@ -75,9 +85,14 @@
      *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
      *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
      *        vibrations associated with incoming calls.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
      */
+    @Deprecated
     public void vibrate(long milliseconds, AudioAttributes attributes) {
-        vibrate(Process.myUid(), mPackageName, milliseconds, attributes);
+        VibrationEffect effect =
+                VibrationEffect.createOneShot(milliseconds, VibrationEffect.DEFAULT_AMPLITUDE);
+        vibrate(effect, attributes);
     }
 
     /**
@@ -99,7 +114,10 @@
      * @param pattern an array of longs of times for which to turn the vibrator on or off.
      * @param repeat the index into pattern at which to repeat, or -1 if
      *        you don't want to repeat.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect)} instead.
      */
+    @Deprecated
     public void vibrate(long[] pattern, int repeat) {
         vibrate(pattern, repeat, null);
     }
@@ -127,26 +145,34 @@
      *        specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
      *        {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for
      *        vibrations associated with incoming calls.
+     *
+     * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead.
      */
+    @Deprecated
     public void vibrate(long[] pattern, int repeat, AudioAttributes attributes) {
-        vibrate(Process.myUid(), mPackageName, pattern, repeat, attributes);
+        // This call needs to continue throwing ArrayIndexOutOfBoundsException for compatibility
+        // purposes, whereas VibrationEffect throws an IllegalArgumentException.
+        if (repeat < -1 || repeat >= pattern.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        vibrate(VibrationEffect.createWaveform(pattern, repeat), attributes);
+    }
+
+    public void vibrate(VibrationEffect vibe) {
+        vibrate(vibe, null);
+    }
+
+    public void vibrate(VibrationEffect vibe, AudioAttributes attributes) {
+        vibrate(Process.myUid(), mPackageName, vibe, attributes);
     }
 
     /**
+     * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allowing the caller to specify
+     * that the vibration is owned by someone else.
      * @hide
-     * Like {@link #vibrate(long, AudioAttributes)}, but allowing the caller to specify that
-     * the vibration is owned by someone else.
      */
-    public abstract void vibrate(int uid, String opPkg, long milliseconds,
-            AudioAttributes attributes);
-
-    /**
-     * @hide
-     * Like {@link #vibrate(long[], int, AudioAttributes)}, but allowing the caller to specify that
-     * the vibration is owned by someone else.
-     */
-    public abstract void vibrate(int uid, String opPkg, long[] pattern, int repeat,
-            AudioAttributes attributes);
+    public abstract void vibrate(int uid, String opPkg,
+            VibrationEffect vibe, AudioAttributes attributes);
 
     /**
      * Turn the vibrator off.
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 92c70bd..6d4281b 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -201,6 +201,29 @@
         public void clearError() {
             mLastWtf = null;
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            // Not using ByteBuffer.equals since it takes buffer position into account and we
+            // always use absolute positions here.
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            Event other = (Event) o;
+            return Arrays.equals(mBuffer.array(), other.mBuffer.array());
+        }
+
+        /**
+         * @hide
+         */
+        @Override
+        public int hashCode() {
+            // Not using ByteBuffer.hashCode since it takes buffer position into account and we
+            // always use absolute positions here.
+            return Arrays.hashCode(mBuffer.array());
+        }
     }
 
     // We assume that the native methods deal with any concurrency issues.
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 0906d1a..81c2f5d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -198,7 +198,9 @@
             if (sProviderInstance != null) return sProviderInstance;
 
             final int uid = android.os.Process.myUid();
-            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
+            if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
+                    || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
+                    || uid == android.os.Process.BLUETOOTH_UID) {
                 throw new UnsupportedOperationException(
                         "For security reasons, WebView is not allowed in privileged processes");
             }
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9b2aba3..67a3aee 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2508,6 +2508,9 @@
     <!-- How long history of previous vibrations should be kept for the dumpsys. -->
     <integer name="config_previousVibrationsDumpLimit">20</integer>
 
+    <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
+    <integer name="config_defaultVibrationAmplitude">255</integer>
+
     <!-- Number of retries Cell Data should attempt for a given error code before
          restarting the modem.
          Error codes not listed will not lead to modem restarts.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6acccd1a..f9fe333 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1816,6 +1816,7 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+  <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="integer" name="config_radioScanningTimeout" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 68d3dd5..8823a92 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -33,65 +33,10 @@
 
 const int Tree::MAX_CACHED_BITMAP_SIZE = 2048;
 
-void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY,
-        bool useStagingData) {
-    float matrixScale = getMatrixScale(groupStackedMatrix);
-    if (matrixScale == 0) {
-        // When either x or y is scaled to 0, we don't need to draw anything.
-        return;
-    }
-
-    SkMatrix pathMatrix(groupStackedMatrix);
-    pathMatrix.postScale(scaleX, scaleY);
-
-    //TODO: try apply the path matrix to the canvas instead of creating a new path.
-    SkPath renderPath;
-    renderPath.reset();
-
-    if (useStagingData) {
-        SkPath tmpPath;
-        getStagingPath(&tmpPath);
-        renderPath.addPath(tmpPath, pathMatrix);
-    } else {
-        renderPath.addPath(getUpdatedPath(), pathMatrix);
-    }
-
-    float minScale = fmin(scaleX, scaleY);
-    float strokeScale = minScale * matrixScale;
-    drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData);
-}
-
 void Path::dump() {
     ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size());
 }
 
-float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) {
-    // Given unit vectors A = (0, 1) and B = (1, 0).
-    // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
-    // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
-    // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
-    // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
-    //
-    // For non-skew case, which is most of the cases, matrix scale is computing exactly the
-    // scale on x and y axis, and take the minimal of these two.
-    // For skew case, an unit square will mapped to a parallelogram. And this function will
-    // return the minimal height of the 2 bases.
-    SkVector skVectors[2];
-    skVectors[0].set(0, 1);
-    skVectors[1].set(1, 0);
-    groupStackedMatrix.mapVectors(skVectors, 2);
-    float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY);
-    float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY);
-    float crossProduct = skVectors[0].cross(skVectors[1]);
-    float maxScale = fmax(scaleX, scaleY);
-
-    float matrixScale = 0;
-    if (maxScale > 0) {
-        matrixScale = fabs(crossProduct) / maxScale;
-    }
-    return matrixScale;
-}
-
 // Called from UI thread during the initial setup/theme change.
 Path::Path(const char* pathStr, size_t strLength) {
     PathParser::ParseResult result;
@@ -104,18 +49,19 @@
     mStagingProperties.syncProperties(path.mStagingProperties);
 }
 
-const SkPath& Path::getUpdatedPath() {
-    if (mSkPathDirty) {
-        mSkPath.reset();
-        VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
-        mSkPathDirty = false;
+const SkPath& Path::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+    if (useStagingData) {
+        tempStagingPath->reset();
+        VectorDrawableUtils::verbsToPath(tempStagingPath, mStagingProperties.getData());
+        return *tempStagingPath;
+    } else {
+        if (mSkPathDirty) {
+            mSkPath.reset();
+            VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData());
+            mSkPathDirty = false;
+        }
+        return mSkPath;
     }
-    return mSkPath;
-}
-
-void Path::getStagingPath(SkPath* outPath) {
-    outPath->reset();
-    VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData());
 }
 
 void Path::syncProperties() {
@@ -157,26 +103,35 @@
     }
 }
 
-const SkPath& FullPath::getUpdatedPath() {
-    if (!mSkPathDirty && !mProperties.mTrimDirty) {
+const SkPath& FullPath::getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) {
+    if (!useStagingData && !mSkPathDirty && !mProperties.mTrimDirty) {
         return mTrimmedSkPath;
     }
-    Path::getUpdatedPath();
-    if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
-        mProperties.mTrimDirty = false;
-        applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
-                mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
-        return mTrimmedSkPath;
+    Path::getUpdatedPath(useStagingData, tempStagingPath);
+    SkPath *outPath;
+    if (useStagingData) {
+        SkPath inPath = *tempStagingPath;
+        applyTrim(tempStagingPath, inPath, mStagingProperties.getTrimPathStart(),
+                mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+        outPath = tempStagingPath;
     } else {
-        return mSkPath;
+        if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) {
+            mProperties.mTrimDirty = false;
+            applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(),
+                    mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset());
+            outPath = &mTrimmedSkPath;
+        } else {
+            outPath = &mSkPath;
+        }
     }
-}
-
-void FullPath::getStagingPath(SkPath* outPath) {
-    Path::getStagingPath(outPath);
-    SkPath inPath = *outPath;
-    applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(),
-            mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset());
+    const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+    bool setFillPath = properties.getFillGradient() != nullptr
+            || properties.getFillColor() != SK_ColorTRANSPARENT;
+    if (setFillPath) {
+        SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
+        outPath->setFillType(ft);
+    }
+    return *outPath;
 }
 
 void FullPath::dump() {
@@ -192,16 +147,17 @@
     return SkColorSetA(color, alphaBytes * alpha);
 }
 
-void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale,
-                        const SkMatrix& matrix, bool useStagingData){
+void FullPath::draw(SkCanvas* outCanvas, bool useStagingData) {
     const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties;
+    SkPath tempStagingPath;
+    const SkPath& renderPath = getUpdatedPath(useStagingData, &tempStagingPath);
 
     // Draw path's fill, if fill color or gradient is valid
     bool needsFill = false;
     SkPaint paint;
     if (properties.getFillGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha()));
-        paint.setShader(properties.getFillGradient()->makeWithLocalMatrix(matrix));
+        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getFillGradient())));
         needsFill = true;
     } else if (properties.getFillColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha()));
@@ -211,8 +167,6 @@
     if (needsFill) {
         paint.setStyle(SkPaint::Style::kFill_Style);
         paint.setAntiAlias(true);
-        SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType());
-        renderPath.setFillType(ft);
         outCanvas->drawPath(renderPath, paint);
     }
 
@@ -220,7 +174,7 @@
     bool needsStroke = false;
     if (properties.getStrokeGradient() != nullptr) {
         paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha()));
-        paint.setShader(properties.getStrokeGradient()->makeWithLocalMatrix(matrix));
+        paint.setShader(sk_sp<SkShader>(SkSafeRef(properties.getStrokeGradient())));
         needsStroke = true;
     } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) {
         paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha()));
@@ -232,7 +186,7 @@
         paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin()));
         paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap()));
         paint.setStrokeMiter(properties.getStrokeMiterLimit());
-        paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale);
+        paint.setStrokeWidth(properties.getStrokeWidth());
         outCanvas->drawPath(renderPath, paint);
     }
 }
@@ -306,36 +260,28 @@
     }
 }
 
-void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath,
-        float strokeScale, const SkMatrix& matrix, bool useStagingData){
-    outCanvas->clipPath(renderPath);
+void ClipPath::draw(SkCanvas* outCanvas, bool useStagingData) {
+    SkPath tempStagingPath;
+    outCanvas->clipPath(getUpdatedPath(useStagingData, &tempStagingPath));
 }
 
 Group::Group(const Group& group) : Node(group) {
     mStagingProperties.syncProperties(group.mStagingProperties);
 }
 
-void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
-        float scaleY, bool useStagingData) {
-    // TODO: Try apply the matrix to the canvas instead of passing it down the tree
-
-    // Calculate current group's matrix by preConcat the parent's and
-    // and the current one on the top of the stack.
-    // Basically the Mfinal = Mviewport * M0 * M1 * M2;
-    // Mi the local matrix at level i of the group tree.
+void Group::draw(SkCanvas* outCanvas, bool useStagingData) {
+    // Save the current clip and matrix information, which is local to this group.
+    SkAutoCanvasRestore saver(outCanvas, true);
+    // apply the current group's matrix to the canvas
     SkMatrix stackedMatrix;
     const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties;
     getLocalMatrix(&stackedMatrix, prop);
-    stackedMatrix.postConcat(currentMatrix);
-
-    // Save the current clip information, which is local to this group.
-    outCanvas->save();
+    outCanvas->concat(stackedMatrix);
     // Draw the group tree in the same order as the XML file.
     for (auto& child : mChildren) {
-        child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData);
+        child->draw(outCanvas, useStagingData);
     }
-    // Restore the previous clip information.
-    outCanvas->restore();
+    // Restore the previous clip and matrix information.
 }
 
 void Group::dump() {
@@ -556,7 +502,8 @@
             mStagingProperties.getViewportHeight() : mProperties.getViewportHeight();
     float scaleX = outCache.width() / viewportWidth;
     float scaleY = outCache.height() / viewportHeight;
-    mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData);
+    outCanvas.scale(scaleX, scaleY);
+    mRootNode->draw(&outCanvas, useStagingData);
 }
 
 bool Tree::allocateBitmapIfNeeded(Cache& cache, int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 8244a39..729a4dd 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -109,8 +109,7 @@
         mName = node.mName;
     }
     Node() {}
-    virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
-            float scaleX, float scaleY, bool useStagingData) = 0;
+    virtual void draw(SkCanvas* outCanvas, bool useStagingData) = 0;
     virtual void dump() = 0;
     void setName(const char* name) {
         mName = name;
@@ -169,9 +168,6 @@
     Path() {}
 
     void dump() override;
-    void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
-            float scaleX, float scaleY, bool useStagingData) override;
-    static float getMatrixScale(const SkMatrix& groupStackedMatrix);
     virtual void syncProperties() override;
     virtual void onPropertyChanged(Properties* prop) override {
         if (prop == &mStagingProperties) {
@@ -193,10 +189,7 @@
     PathProperties* mutateProperties() { return &mProperties; }
 
 protected:
-    virtual const SkPath& getUpdatedPath();
-    virtual void getStagingPath(SkPath* outPath);
-    virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
-            float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0;
+    virtual const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath);
 
     // Internal data, render thread only.
     bool mSkPathDirty = true;
@@ -364,6 +357,7 @@
     FullPath(const FullPath& path); // for cloning
     FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
     FullPath() : Path() {}
+    void draw(SkCanvas* outCanvas, bool useStagingData) override;
     void dump() override;
     FullPathProperties* mutateStagingProperties() { return &mStagingProperties; }
     const FullPathProperties* stagingProperties() { return &mStagingProperties; }
@@ -387,10 +381,7 @@
     }
 
 protected:
-    const SkPath& getUpdatedPath() override;
-    void getStagingPath(SkPath* outPath) override;
-    void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
-            float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+    const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override;
 private:
 
     FullPathProperties mProperties = FullPathProperties(this);
@@ -407,10 +398,7 @@
     ClipPath(const ClipPath& path) : Path(path) {}
     ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
     ClipPath() : Path() {}
-
-protected:
-    void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
-            float strokeScale, const SkMatrix& matrix, bool useStagingData) override;
+    void draw(SkCanvas* outCanvas, bool useStagingData) override;
 };
 
 class ANDROID_API Group: public Node {
@@ -519,8 +507,7 @@
     GroupProperties* mutateProperties() { return &mProperties; }
 
     // Methods below could be called from either UI thread or Render Thread.
-    virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
-            float scaleX, float scaleY, bool useStagingData) override;
+    virtual void draw(SkCanvas* outCanvas, bool useStagingData) override;
     void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties);
     void dump() override;
     static bool isValidProperty(int propertyId);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 79429ec..a895cba 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -206,10 +206,10 @@
         return new T();
     }
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
-        return sk_sp<SkSurface>();
+        return nullptr;
     }
-    sk_sp<SkImage> onNewImageSnapshot(SkBudgeted) override {
-        return sk_sp<SkImage>();
+    sk_sp<SkImage> onNewImageSnapshot() override {
+        return nullptr;
     }
     T* canvas() { return static_cast<T*>(getCanvas()); }
     void onCopyOnWrite(ContentChangeMode) override {}
diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp
index 8e0d3ee..6f264e1 100644
--- a/libs/hwui/tests/unit/VectorDrawableTests.cpp
+++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp
@@ -347,51 +347,6 @@
     }
 }
 
-TEST(VectorDrawable, matrixScale) {
-    struct MatrixAndScale {
-        float buffer[9];
-        float matrixScale;
-    };
-
-    const MatrixAndScale sMatrixAndScales[] {
-        {
-            {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f},
-            1.0
-        },
-        {
-            {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f},
-            1.0f,
-        },
-        {
-            {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f},
-            1.5f,
-        },
-        {
-            {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f},
-            0.99999994f,
-        },
-        {
-            {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f},
-            0.99999994f,
-        },
-        {
-            {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f},
-            0.9999999f,
-        },
-        {
-            {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f},
-            1.0000001f,
-        },
-    };
-
-    for (MatrixAndScale matrixAndScale : sMatrixAndScales) {
-        SkMatrix matrix;
-        matrix.set9(matrixAndScale.buffer);
-        float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix);
-        EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale);
-    }
-}
-
 TEST(VectorDrawable, groupProperties) {
     //TODO: Also need to test property sync and dirty flag when properties change.
     VectorDrawable::Group group;
@@ -458,7 +413,7 @@
 
     // Setting the fill gradient increments the ref count of the shader by 1
     path.mutateStagingProperties()->setFillGradient(shader);
-    path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true);
+    path.draw(&canvas, true);
     // Resetting the fill gradient decrements the ref count of the shader by 1
     path.mutateStagingProperties()->setFillGradient(nullptr);
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 72bdbf1..5ffc8f9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -726,6 +726,10 @@
     <!-- The alpha to apply to the recents row when it doesn't have focus -->
     <item name="recents_recents_row_dim_alpha" format="float" type="dimen">0.5</item>
 
+    <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
+         loading full resolution screenshots. -->
+    <dimen name="recents_fast_fling_velocity">600dp</dimen>
+
     <!-- The size of the PIP drag-to-dismiss target. -->
     <dimen name="pip_dismiss_target_size">48dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index d3e939f..6da8272 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -33,6 +33,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -59,6 +60,7 @@
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.HighResThumbnailLoader;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
@@ -184,6 +186,7 @@
         return sTaskLoader;
     }
 
+
     public static SystemServicesProxy getSystemServices() {
         return sSystemServicesProxy;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c9debb2..f0a9bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -375,6 +375,8 @@
 
         // Notify of the next draw
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
+
+        Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(true);
     }
 
     @Override
@@ -529,6 +531,7 @@
         mReceivedNewIntent = false;
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
         MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
+        Recents.getTaskLoader().getHighResThumbnailLoader().setVisible(false);
 
         if (!isChangingConfigurations()) {
             // Workaround for b/22542869, if the RecentsActivity is started again, but without going
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index d7c1391..25eea95 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -637,7 +637,7 @@
     }
 
     /** Returns the top task thumbnail for the given task id */
-    public ThumbnailData getTaskThumbnail(int taskId) {
+    public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
         if (mAm == null) return null;
 
         // If we are mocking, then just return a dummy thumbnail
@@ -649,7 +649,7 @@
             return thumbnailData;
         }
 
-        ThumbnailData thumbnailData = getThumbnail(taskId);
+        ThumbnailData thumbnailData = getThumbnail(taskId, reduced);
         if (thumbnailData.thumbnail != null && !ActivityManager.ENABLE_TASK_SNAPSHOTS) {
             thumbnailData.thumbnail.setHasAlpha(false);
             // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -669,7 +669,7 @@
     /**
      * Returns a task thumbnail from the activity manager
      */
-    public @NonNull ThumbnailData getThumbnail(int taskId) {
+    public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
         if (mAm == null) {
             return new ThumbnailData();
         }
@@ -678,7 +678,8 @@
         if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
             ActivityManager.TaskSnapshot snapshot = null;
             try {
-                snapshot = ActivityManager.getService().getTaskSnapshot(taskId);
+                snapshot = ActivityManager.getService().getTaskSnapshot(taskId,
+                        false /* reducedResolution */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to retrieve snapshot", e);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
new file mode 100644
index 0000000..be8da9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -0,0 +1,215 @@
+/*
+ * 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.systemui.recents.model;
+
+import static android.os.Process.setThreadPriority;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskCallbacks;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Loader class that loads full-resolution thumbnails when appropriate.
+ */
+public class HighResThumbnailLoader implements TaskCallbacks {
+
+    @GuardedBy("mLoadQueue")
+    private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
+    @GuardedBy("mLoadQueue")
+    private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
+    @GuardedBy("mLoadQueue")
+    private boolean mLoaderIdling;
+
+    private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+    private final Thread mLoadThread;
+    private final Handler mMainThreadHandler;
+    private final SystemServicesProxy mSystemServicesProxy;
+    private boolean mLoading;
+    private boolean mVisible;
+    private boolean mFlingingFast;
+
+    public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper) {
+        mMainThreadHandler = new Handler(looper);
+        mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
+        mLoadThread.start();
+        mSystemServicesProxy = ssp;
+    }
+
+    public void setVisible(boolean visible) {
+        mVisible = visible;
+        updateLoading();
+    }
+
+    public void setFlingingFast(boolean flingingFast) {
+        if (mFlingingFast == flingingFast) {
+            return;
+        }
+        mFlingingFast = flingingFast;
+        updateLoading();
+    }
+
+    @VisibleForTesting
+    boolean isLoading() {
+        return mLoading;
+    }
+
+    private void updateLoading() {
+        setLoading(mVisible && !mFlingingFast);
+    }
+
+    private void setLoading(boolean loading) {
+        if (loading == mLoading) {
+            return;
+        }
+        synchronized (mLoadQueue) {
+            mLoading = loading;
+            if (!loading) {
+                stopLoading();
+            } else {
+                startLoading();
+            }
+        }
+    }
+
+    @GuardedBy("mLoadQueue")
+    private void startLoading() {
+        for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
+            Task t = mVisibleTasks.get(i);
+            if ((t.thumbnail == null || t.thumbnail.reducedResolution)
+                    && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
+                mLoadQueue.add(t);
+            }
+        }
+        mLoadQueue.notifyAll();
+    }
+
+    @GuardedBy("mLoadQueue")
+    private void stopLoading() {
+        mLoadQueue.clear();
+        mLoadQueue.notifyAll();
+    }
+
+    /**
+     * Needs to be called when a task becomes visible. Note that this is different from
+     * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
+     * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
+     * has been updated.
+     */
+    public void onTaskVisible(Task t) {
+        t.addCallback(this);
+        mVisibleTasks.add(t);
+        if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
+            synchronized (mLoadQueue) {
+                mLoadQueue.add(t);
+                mLoadQueue.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
+     * different from {@link TaskCallbacks#onTaskDataUnloaded()}
+     */
+    public void onTaskInvisible(Task t) {
+        t.removeCallback(this);
+        mVisibleTasks.remove(t);
+        synchronized (mLoadQueue) {
+            mLoadQueue.remove(t);
+        }
+    }
+
+    @VisibleForTesting
+    void waitForLoaderIdle() {
+        while (true) {
+            synchronized (mLoadQueue) {
+                if (mLoadQueue.isEmpty() && mLoaderIdling) {
+                    return;
+                }
+            }
+            SystemClock.sleep(100);
+        }
+    }
+
+    @Override
+    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+        if (thumbnailData != null && !thumbnailData.reducedResolution) {
+            synchronized (mLoadQueue) {
+                mLoadQueue.remove(task);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskDataUnloaded() {
+    }
+
+    @Override
+    public void onTaskStackIdChanged() {
+    }
+
+    private final Runnable mLoader = new Runnable() {
+
+        @Override
+        public void run() {
+            setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
+            while (true) {
+                Task next = null;
+                synchronized (mLoadQueue) {
+                    if (!mLoading || mLoadQueue.isEmpty()) {
+                        try {
+                            mLoaderIdling = true;
+                            mLoadQueue.wait();
+                            mLoaderIdling = false;
+                        } catch (InterruptedException e) {
+                            // Don't care.
+                        }
+                    } else {
+                        next = mLoadQueue.poll();
+                        if (next != null) {
+                            mLoadingTasks.add(next);
+                        }
+                    }
+                }
+                if (next != null) {
+                    loadTask(next);
+                }
+            }
+        }
+
+        private void loadTask(Task t) {
+            ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
+                    false /* reducedResolution */);
+            mMainThreadHandler.post(() -> {
+                synchronized (mLoadQueue) {
+                    mLoadingTasks.remove(t);
+                }
+                if (mVisibleTasks.contains(t)) {
+                    t.notifyTaskDataLoaded(thumbnail, t.icon);
+                }
+            });
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 12c10df..f8d123b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -188,7 +188,8 @@
             Drawable icon = isStackTask
                     ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
                     : null;
-            Bitmap thumbnail = loader.getAndUpdateThumbnail(taskKey, false /* loadIfNotCached */);
+            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                    false /* loadIfNotCached */);
             int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
             int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
             boolean isSystemApp = (info != null) &&
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 40a4a2b..e378d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -27,6 +27,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.util.Log;
 import android.util.LruCache;
 
@@ -37,6 +38,7 @@
 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.Task.TaskKey;
 
 import java.io.PrintWriter;
 import java.util.Map;
@@ -156,7 +158,6 @@
                     }
                 }
             } else {
-                RecentsConfiguration config = Recents.getConfiguration();
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 // If we've stopped the loader, then fall through to the above logic to wait on
                 // the load thread
@@ -190,7 +191,8 @@
                         }
 
                         if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-                        ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
+                        ThumbnailData cachedThumbnailData = ssp.getTaskThumbnail(t.key.id,
+                                true /* reducedResolution */);
 
                         if (cachedThumbnailData.thumbnail == null) {
                             cachedThumbnailData.thumbnail = mDefaultThumbnail;
@@ -242,6 +244,7 @@
     private final TaskKeyLruCache<String> mContentDescriptionCache;
     private final TaskResourceLoadQueue mLoadQueue;
     private final BackgroundTaskLoader mLoader;
+    private final HighResThumbnailLoader mHighResThumbnailLoader;
 
     private final int mMaxThumbnailCacheSize;
     private final int mMaxIconCacheSize;
@@ -293,6 +296,8 @@
                 mClearActivityInfoOnEviction);
         mActivityInfoCache = new LruCache(numRecentTasks);
         mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultThumbnail, mDefaultIcon);
+        mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
+                Looper.getMainLooper());
     }
 
     /** Returns the size of the app icon cache. */
@@ -305,6 +310,10 @@
         return mMaxThumbnailCacheSize;
     }
 
+    public HighResThumbnailLoader getHighResThumbnailLoader() {
+        return mHighResThumbnailLoader;
+    }
+
     /** Creates a new plan for loading the recent tasks. */
     public RecentsTaskLoadPlan createLoadPlan(Context context) {
         RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
@@ -346,7 +355,7 @@
     /** Releases the task resource data back into the pool. */
     public void unloadTaskData(Task t) {
         mLoadQueue.removeTask(t);
-        t.notifyTaskDataUnloaded(null, mDefaultIcon);
+        t.notifyTaskDataUnloaded(mDefaultIcon);
     }
 
     /** Completely removes the resource data from the pool. */
@@ -356,7 +365,7 @@
         mActivityLabelCache.remove(t.key);
         mContentDescriptionCache.remove(t.key);
         if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(null, mDefaultIcon);
+            t.notifyTaskDataUnloaded(mDefaultIcon);
         }
     }
 
@@ -491,16 +500,16 @@
     /**
      * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
      */
-    Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
+    ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         if (loadIfNotCached) {
             RecentsConfiguration config = Recents.getConfiguration();
             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
                 // Load the thumbnail from the system
-                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id);
+                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, true /* reducedResolution */);
                 if (thumbnailData.thumbnail != null) {
-                    return thumbnailData.thumbnail;
+                    return thumbnailData;
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 2f2e866..29d0a23 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -17,6 +17,7 @@
 package com.android.systemui.recents.model;
 
 import android.app.ActivityManager;
+import android.app.ActivityManager.TaskThumbnail;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -141,7 +142,7 @@
      * which can then fall back to the application icon.
      */
     public Drawable icon;
-    public Bitmap thumbnail;
+    public ThumbnailData thumbnail;
     @ViewDebug.ExportedProperty(category="recents")
     public String title;
     @ViewDebug.ExportedProperty(category="recents")
@@ -199,11 +200,11 @@
     }
 
     public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
-                Bitmap thumbnail, String title, String titleDescription, String dismissDescription,
-                String appInfoDescription, int colorPrimary, int colorBackground,
-                boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
-                boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
-                int resizeMode, ComponentName topActivity, boolean isLocked) {
+            ThumbnailData thumbnail, String title, String titleDescription,
+            String dismissDescription, String appInfoDescription, int colorPrimary,
+            int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
+            boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
+            int resizeMode, ComponentName topActivity, boolean isLocked) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
         this.key = key;
@@ -301,7 +302,7 @@
     /** Notifies the callback listeners that this task has been loaded */
     public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
         this.icon = applicationIcon;
-        this.thumbnail = thumbnailData != null ? thumbnailData.thumbnail : null;
+        this.thumbnail = thumbnailData;
         int callbackCount = mCallbacks.size();
         for (int i = 0; i < callbackCount; i++) {
             mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
@@ -309,9 +310,9 @@
     }
 
     /** Notifies the callback listeners that this task has been unloaded */
-    public void notifyTaskDataUnloaded(Bitmap defaultThumbnail, Drawable defaultApplicationIcon) {
+    public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
         icon = defaultApplicationIcon;
-        thumbnail = defaultThumbnail;
+        thumbnail = null;
         for (int i = mCallbacks.size() - 1; i >= 0; i--) {
             mCallbacks.get(i).onTaskDataUnloaded();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
index 09a3712..33ff1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
@@ -29,12 +29,16 @@
     public Bitmap thumbnail;
     public int orientation;
     public final Rect insets = new Rect();
+    public boolean reducedResolution;
+    public float scale;
 
     public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
         ThumbnailData out = new ThumbnailData();
         out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
         out.insets.set(snapshot.getContentInsets());
         out.orientation = snapshot.getOrientation();
+        out.reducedResolution = snapshot.isReducedResolution();
+        out.scale = snapshot.getScale();
         return out;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 40aad45..b7cedf7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -83,8 +83,8 @@
 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
@@ -96,10 +96,10 @@
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-
 import com.android.systemui.recents.views.grid.GridTaskView;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -217,6 +217,9 @@
     // grid layout.
     private TaskViewFocusFrame mTaskViewFocusFrame;
 
+    private Task mPrefetchingTask;
+    private final float mFastFlingVelocity;
+
     // A convenience update listener to request updating clipping of tasks
     private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
             new ValueAnimator.AnimatorUpdateListener() {
@@ -273,6 +276,7 @@
         mTaskCornerRadiusPx = Recents.getConfiguration().isGridEnabled ?
                 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
                 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+        mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mDividerSize = ssp.getDockedDividerSize(context);
         mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
         mDisplayRect = ssp.getDisplayRect();
@@ -663,6 +667,8 @@
             }
         }
 
+        updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]);
+
         // Update the focus if the previous focused task was returned to the view pool
         if (lastFocusedTaskIndex != -1) {
             int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
@@ -1200,6 +1206,8 @@
         if (mStackScroller.computeScroll()) {
             // Notify accessibility
             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            Recents.getTaskLoader().getHighResThumbnailLoader().setFlingingFast(
+                    mStackScroller.getScrollVelocity() > mFastFlingVelocity);
         }
         if (mDeferredTaskViewLayoutAnimation != null) {
             relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
@@ -1657,13 +1665,41 @@
             tv.setNoUserInteractionState();
         }
 
-        // Load the task data
-        Recents.getTaskLoader().loadTaskData(task);
+        if (task == mPrefetchingTask) {
+            task.notifyTaskDataLoaded(task.thumbnail, task.icon);
+        } else {
+            // Load the task data
+            Recents.getTaskLoader().loadTaskData(task);
+        }
+        Recents.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task);
     }
 
     private void unbindTaskView(TaskView tv, Task task) {
-        // Report that this task's data is no longer being used
-        Recents.getTaskLoader().unloadTaskData(task);
+        if (task != mPrefetchingTask) {
+            // Report that this task's data is no longer being used
+            Recents.getTaskLoader().unloadTaskData(task);
+        }
+        Recents.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task);
+    }
+
+    private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) {
+        Task t = null;
+        boolean somethingVisible = frontIndex != -1 && backIndex != -1;
+        if (somethingVisible && frontIndex < tasks.size() - 1) {
+            t = tasks.get(frontIndex + 1);
+        }
+        if (mPrefetchingTask != t) {
+            if (mPrefetchingTask != null) {
+                int index = tasks.indexOf(mPrefetchingTask);
+                if (index < backIndex || index > frontIndex) {
+                    Recents.getTaskLoader().unloadTaskData(mPrefetchingTask);
+                }
+            }
+            mPrefetchingTask = t;
+            if (t != null) {
+                Recents.getTaskLoader().loadTaskData(t);
+            }
+        }
     }
 
     /**** TaskViewCallbacks Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 1fa73c6..8cd3d15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -262,6 +262,10 @@
         return !mScroller.isFinished();
     }
 
+    float getScrollVelocity() {
+        return mScroller.getCurrVelocity();
+    }
+
     /** Stops the scroller and any current fling. */
     void stopScroller() {
         if (!mScroller.isFinished()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index e0dac09..5989b33 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -66,7 +66,7 @@
     protected Rect mThumbnailRect = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
     protected float mThumbnailScale;
-    private float mFullscreenThumbnailScale;
+    private float mFullscreenThumbnailScale = 1f;
     /** The height, in pixels, of the task view's title bar. */
     private int mTitleBarHeight;
     private boolean mSizeToFit = false;
@@ -116,12 +116,6 @@
         mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
         mBgFillPaint.setColor(Color.WHITE);
         mLockedPaint.setColor(Color.WHITE);
-        if (ActivityManager.ENABLE_TASK_SNAPSHOTS) {
-            mFullscreenThumbnailScale = 1f;
-        } else {
-            mFullscreenThumbnailScale = res.getFraction(
-                    com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
-        }
         mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
     }
 
@@ -190,6 +184,7 @@
         if (thumbnailData != null && thumbnailData.thumbnail != null) {
             Bitmap bm = thumbnailData.thumbnail;
             bm.prepareToDraw();
+            mFullscreenThumbnailScale = thumbnailData.scale;
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
             mDrawPaint.setShader(mBitmapShader);
             mThumbnailRect.set(0, 0,
@@ -297,7 +292,8 @@
                         (float) mTaskViewRect.width() / mThumbnailRect.width(),
                         (float) mTaskViewRect.height() / mThumbnailRect.height());
             }
-            mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
+            mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
+                    -mThumbnailData.insets.top * mFullscreenThumbnailScale);
             mMatrix.postScale(mThumbnailScale, mThumbnailScale);
             mBitmapShader.setLocalMatrix(mMatrix);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 2b52b48..5fb642f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -31,6 +31,7 @@
 import android.app.IActivityManager;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -107,6 +108,7 @@
     private int mNavigationBarMode;
     private AccessibilityManager mAccessibilityManager;
     private MagnificationContentObserver mMagnificationObserver;
+    private ContentResolver mContentResolver;
 
     private int mDisabledFlags1;
     private StatusBar mStatusBar;
@@ -138,9 +140,10 @@
         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
         mAccessibilityManager.addAccessibilityServicesStateChangeListener(
                 this::updateAccessibilityServicesState);
+        mContentResolver = getContext().getContentResolver();
         mMagnificationObserver = new MagnificationContentObserver(
                 getContext().getMainThreadHandler());
-        getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+        mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
                 mMagnificationObserver);
 
@@ -163,7 +166,7 @@
         mCommandQueue.removeCallbacks(this);
         mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
                 this::updateAccessibilityServicesState);
-        getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver);
+        mContentResolver.unregisterContentObserver(mMagnificationObserver);
         try {
             WindowManagerGlobal.getWindowManagerService()
                     .removeRotationWatcher(mRotationWatcher);
@@ -563,7 +566,7 @@
     private void updateAccessibilityServicesState() {
         int requestingServices = 0;
         try {
-            if (Settings.Secure.getInt(getContext().getContentResolver(),
+            if (Settings.Secure.getInt(mContentResolver,
                     Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) {
                 requestingServices++;
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
new file mode 100644
index 0000000..4d632af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.systemui.recents.model;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task.TaskKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighResThumbnailLoaderTest extends SysuiTestCase {
+
+    private HighResThumbnailLoader mLoader;
+
+    @Mock
+    private SystemServicesProxy mMockSystemServicesProxy;
+    @Mock
+    private Task mTask;
+
+    private ThumbnailData mThumbnailData = new ThumbnailData();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mLoader = new HighResThumbnailLoader(mMockSystemServicesProxy, Looper.getMainLooper());
+        mTask.key = new TaskKey(0, 0, null, 0, 0, 0);
+        when(mMockSystemServicesProxy.getTaskThumbnail(anyInt(), anyBoolean()))
+                .thenReturn(mThumbnailData);
+        mLoader.setVisible(true);
+    }
+
+    @Test
+    public void testLoading() throws Exception {
+        mLoader.setVisible(true);
+        assertTrue(mLoader.isLoading());
+        mLoader.setVisible(false);
+        assertFalse(mLoader.isLoading());
+        mLoader.setVisible(true);
+        mLoader.setFlingingFast(true);
+        assertFalse(mLoader.isLoading());
+        mLoader.setFlingingFast(false);
+        assertTrue(mLoader.isLoading());
+    }
+
+    @Test
+    public void testLoad() throws Exception {
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    @Test
+    public void testFlinging_notLoaded() throws Exception {
+        mLoader.setFlingingFast(true);
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    /**
+     * Tests whether task is loaded after stopping to fling
+     */
+    @Test
+    public void testAfterFlinging() throws Exception {
+        mLoader.setFlingingFast(true);
+        mLoader.onTaskVisible(mTask);
+        mLoader.setFlingingFast(false);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    @Test
+    public void testAlreadyLoaded() throws Exception {
+        mTask.thumbnail = new ThumbnailData();
+        mTask.thumbnail.reducedResolution = false;
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+}
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 794ece6..d312902 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -28,6 +28,7 @@
     tzdata_update2 \
     android.hidl.base@1.0-java-static \
     android.hardware.biometrics.fingerprint@2.1-java-static \
+    android.hardware.vibrator@1.0-java-constants \
 
 ifneq ($(INCREMENTAL_BUILDS),)
     LOCAL_PROGUARD_ENABLED := disabled
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 5fe6952..c4676d1 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -22,8 +22,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.input.InputManager;
+import android.hardware.vibrator.V1_0.Constants.EffectStrength;
 import android.media.AudioManager;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
@@ -42,6 +44,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -67,12 +70,14 @@
     private static final boolean DEBUG = false;
     private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
 
-    private final LinkedList<Vibration> mVibrations;
     private final LinkedList<VibrationInfo> mPreviousVibrations;
     private final int mPreviousVibrationsLimit;
-    private Vibration mCurrentVibration;
+    private final boolean mSupportsAmplitudeControl;
+    private final int mDefaultVibrationAmplitude;
+    private final VibrationEffect[] mFallbackEffects;
     private final WorkSource mTmpWorkSource = new WorkSource();
     private final Handler mH = new Handler();
+    private final Object mLock = new Object();
 
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
@@ -81,14 +86,15 @@
     private PowerManagerInternal mPowerManagerInternal;
     private InputManager mIm;
 
-    volatile VibrateThread mThread;
+    private volatile VibrateThread mThread;
 
-    // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+    // mInputDeviceVibrators lock should be acquired after mLock, if both are
     // to be acquired
     private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
     private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
     private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
 
+    private Vibration mCurrentVibration;
     private int mCurVibUid = -1;
     private boolean mLowPowerMode;
     private SettingsObserver mSettingObserver;
@@ -97,106 +103,87 @@
     native static void vibratorInit();
     native static void vibratorOn(long milliseconds);
     native static void vibratorOff();
+    native static boolean vibratorSupportsAmplitudeControl();
+    native static void vibratorSetAmplitude(int amplitude);
+    native static long vibratorPerformEffect(long effect, long strength);
 
     private class Vibration implements IBinder.DeathRecipient {
         private final IBinder mToken;
-        private final long    mTimeout;
-        private final long    mStartTime;
-        private final long[]  mPattern;
-        private final int     mRepeat;
-        private final int     mUsageHint;
-        private final int     mUid;
-        private final String  mOpPkg;
+        private final VibrationEffect mEffect;
+        private final long mStartTime;
+        private final int mUsageHint;
+        private final int mUid;
+        private final String mOpPkg;
 
-        Vibration(IBinder token, long millis, int usageHint, int uid, String opPkg) {
-            this(token, millis, null, 0, usageHint, uid, opPkg);
-        }
-
-        Vibration(IBinder token, long[] pattern, int repeat, int usageHint, int uid,
-                String opPkg) {
-            this(token, 0, pattern, repeat, usageHint, uid, opPkg);
-        }
-
-        private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat, int usageHint, int uid, String opPkg) {
+        private Vibration(IBinder token, VibrationEffect effect,
+                int usageHint, int uid, String opPkg) {
             mToken = token;
-            mTimeout = millis;
+            mEffect = effect;
             mStartTime = SystemClock.uptimeMillis();
-            mPattern = pattern;
-            mRepeat = repeat;
             mUsageHint = usageHint;
             mUid = uid;
             mOpPkg = opPkg;
         }
 
         public void binderDied() {
-            synchronized (mVibrations) {
-                mVibrations.remove(this);
+            synchronized (mLock) {
                 if (this == mCurrentVibration) {
                     doCancelVibrateLocked();
-                    startNextVibrationLocked();
                 }
             }
         }
 
         public boolean hasLongerTimeout(long millis) {
-            if (mTimeout == 0) {
-                // This is a pattern, return false to play the simple
-                // vibration.
-                return false;
+            // If the current effect is a one shot vibration that will end after the given timeout
+            // for the new one shot vibration, then just let the current vibration finish. All
+            // other effect types will get pre-empted.
+            if (mEffect instanceof VibrationEffect.OneShot) {
+                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) mEffect;
+                return mStartTime + oneShot.getTiming() > SystemClock.uptimeMillis() + millis;
             }
-            if ((mStartTime + mTimeout)
-                    < (SystemClock.uptimeMillis() + millis)) {
-                // If this vibration will end before the time passed in, let
-                // the new vibration play.
-                return false;
-            }
-            return true;
+            return false;
         }
 
         public boolean isSystemHapticFeedback() {
+            boolean repeating = false;
+            if (mEffect instanceof VibrationEffect.Waveform) {
+                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) mEffect;
+                repeating = (waveform.getRepeatIndex() < 0);
+            }
             return (mUid == Process.SYSTEM_UID || mUid == 0 || SYSTEM_UI_PACKAGE.equals(mOpPkg))
-                    && mRepeat < 0;
+                    && !repeating;
         }
     }
 
     private static class VibrationInfo {
-        long timeout;
-        long startTime;
-        long[] pattern;
-        int repeat;
-        int usageHint;
-        int uid;
-        String opPkg;
+        private final long mStartTime;
+        private final VibrationEffect mEffect;
+        private final int mUsageHint;
+        private final int mUid;
+        private final String mOpPkg;
 
-        public VibrationInfo(long timeout, long startTime, long[] pattern, int repeat,
+        public VibrationInfo(long startTime, VibrationEffect effect,
                 int usageHint, int uid, String opPkg) {
-            this.timeout = timeout;
-            this.startTime = startTime;
-            this.pattern = pattern;
-            this.repeat = repeat;
-            this.usageHint = usageHint;
-            this.uid = uid;
-            this.opPkg = opPkg;
+            mStartTime = startTime;
+            mEffect = effect;
+            mUsageHint = usageHint;
+            mUid = uid;
+            mOpPkg = opPkg;
         }
 
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("timeout: ")
-                    .append(timeout)
                     .append(", startTime: ")
-                    .append(startTime)
-                    .append(", pattern: ")
-                    .append(Arrays.toString(pattern))
-                    .append(", repeat: ")
-                    .append(repeat)
+                    .append(mStartTime)
+                    .append(", effect: ")
+                    .append(mEffect)
                     .append(", usageHint: ")
-                    .append(usageHint)
+                    .append(mUsageHint)
                     .append(", uid: ")
-                    .append(uid)
+                    .append(mUid)
                     .append(", opPkg: ")
-                    .append(opPkg)
+                    .append(mOpPkg)
                     .toString();
         }
     }
@@ -207,25 +194,38 @@
         // restart instead of a fresh boot.
         vibratorOff();
 
+        mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl();
+
         mContext = context;
-        PowerManager pm = (PowerManager)context.getSystemService(
-                Context.POWER_SERVICE);
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mWakeLock.setReferenceCounted(true);
 
-        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mAppOpsService =
+            IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
         mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                 BatteryStats.SERVICE_NAME));
 
         mPreviousVibrationsLimit = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_previousVibrationsDumpLimit);
 
-        mVibrations = new LinkedList<>();
+        mDefaultVibrationAmplitude = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
         mPreviousVibrations = new LinkedList<>();
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         context.registerReceiver(mIntentReceiver, filter);
+
+        long[] clickEffectTimings = getLongIntArray(context.getResources(),
+                com.android.internal.R.array.config_virtualKeyVibePattern);
+        VibrationEffect clickEffect = VibrationEffect.createWaveform(clickEffectTimings, -1);
+        VibrationEffect doubleClickEffect = VibrationEffect.createWaveform(
+                new long[] {0, 30, 100, 30} /*timings*/, -1);
+
+        mFallbackEffects = new VibrationEffect[] { clickEffect, doubleClickEffect };
+
     }
 
     public void systemReady() {
@@ -242,7 +242,7 @@
 
                     @Override
                     public void onLowPowerModeChanged(PowerSaveState result) {
-                        updateInputDeviceVibrators();
+                        updateVibrators();
                     }
         });
 
@@ -253,11 +253,11 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                updateInputDeviceVibrators();
+                updateVibrators();
             }
         }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
 
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     private final class SettingsObserver extends ContentObserver {
@@ -267,7 +267,7 @@
 
         @Override
         public void onChange(boolean SelfChange) {
-            updateInputDeviceVibrators();
+            updateVibrators();
         }
     }
 
@@ -276,6 +276,15 @@
         return doVibratorExists();
     }
 
+    @Override // Binder call
+    public boolean hasAmplitudeControl() {
+        synchronized (mInputDeviceVibrators) {
+            // Input device vibrators don't support amplitude controls yet, but are still used over
+            // the system vibrator when connected.
+            return mSupportsAmplitudeControl && mInputDeviceVibrators.isEmpty();
+        }
+    }
+
     private void verifyIncomingUid(int uid) {
         if (uid == Binder.getCallingUid()) {
             return;
@@ -287,103 +296,96 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
+    /**
+     * Validate the incoming VibrationEffect.
+     *
+     * We can't throw exceptions here since we might be called from some system_server component,
+     * which would bring the whole system down.
+     *
+     * @return whether the VibrationEffect is valid
+     */
+    private static boolean verifyVibrationEffect(VibrationEffect effect) {
+        if (effect == null) {
+            // Effect must not be null.
+            Slog.wtf(TAG, "effect must not be null");
+            return false;
+        }
+        try {
+            effect.validate();
+        } catch (Exception e) {
+            Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e);
+            return false;
+        }
+        return true;
+    }
+
+    private static long[] getLongIntArray(Resources r, int resid) {
+        int[] ar = r.getIntArray(resid);
+        if (ar == null) {
+            return null;
+        }
+        long[] out = new long[ar.length];
+        for (int i = 0; i < ar.length; i++) {
+            out[i] = ar[i];
+        }
+        return out;
+    }
+
     @Override // Binder call
-    public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
+    public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,
             IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
         }
+        if (token == null) {
+            Slog.e(TAG, "token must not be null");
+            return;
+        }
         verifyIncomingUid(uid);
-        // We're running in the system server so we cannot crash. Check for a
-        // timeout of 0 or negative. This will ensure that a vibration has
-        // either a timeout of > 0 or a non-null pattern.
-        if (milliseconds <= 0 || (mCurrentVibration != null
-                && mCurrentVibration.hasLongerTimeout(milliseconds))) {
-            // Ignore this vibration since the current vibration will play for
-            // longer than milliseconds.
+        if (!verifyVibrationEffect(effect)) {
             return;
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "Vibrating for " + milliseconds + " ms.");
-        }
-
-        Vibration vib = new Vibration(token, milliseconds, usageHint, uid, opPkg);
-
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mVibrations) {
-                removeVibrationLocked(token);
-                doCancelVibrateLocked();
-                addToPreviousVibrationsLocked(vib);
-                startVibrationLocked(vib);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    private boolean isAll0(long[] pattern) {
-        int N = pattern.length;
-        for (int i = 0; i < N; i++) {
-            if (pattern[i] != 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override // Binder call
-    public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
-            int usageHint, IBinder token) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Requires VIBRATE permission");
-        }
-        verifyIncomingUid(uid);
-        // so wakelock calls will succeed
-        long identity = Binder.clearCallingIdentity();
-        try {
-            if (DEBUG) {
-                String s = "";
-                int N = pattern.length;
-                for (int i=0; i<N; i++) {
-                    s += " " + pattern[i];
+        // If our current vibration is longer than the new vibration and is the same amplitude,
+        // then just let the current one finish.
+        if (effect instanceof VibrationEffect.OneShot
+                && mCurrentVibration != null
+                && mCurrentVibration.mEffect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;
+            VibrationEffect.OneShot currentOneShot =
+                    (VibrationEffect.OneShot) mCurrentVibration.mEffect;
+            if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
+                    && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
+                if (DEBUG) {
+                    Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
                 }
-                Slog.d(TAG, "Vibrating with pattern:" + s);
-            }
-
-            // we're running in the server so we can't fail
-            if (pattern == null || pattern.length == 0
-                    || isAll0(pattern)
-                    || repeat >= pattern.length || token == null) {
                 return;
             }
+        }
 
-            Vibration vib = new Vibration(token, pattern, repeat, usageHint, uid, packageName);
+        Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
+
+        // Only link against waveforms since they potentially don't have a finish if
+        // they're repeating. Let other effects just play out until they're done.
+        if (effect instanceof VibrationEffect.Waveform) {
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
                 return;
             }
+        }
 
-            synchronized (mVibrations) {
-                removeVibrationLocked(token);
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
                 doCancelVibrateLocked();
-                if (repeat >= 0) {
-                    mVibrations.addFirst(vib);
-                    startNextVibrationLocked();
-                } else {
-                    // A negative repeat means that this pattern is not meant
-                    // to repeat. Treat it like a simple vibration.
-                    startVibrationLocked(vib);
-                }
+                startVibrationLocked(vib);
                 addToPreviousVibrationsLocked(vib);
             }
-        }
-        finally {
-            Binder.restoreCallingIdentity(identity);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
@@ -391,8 +393,8 @@
         if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
             mPreviousVibrations.removeFirst();
         }
-        mPreviousVibrations.addLast(new VibratorService.VibrationInfo(vib.mTimeout, vib.mStartTime,
-                vib.mPattern, vib.mRepeat, vib.mUsageHint, vib.mUid, vib.mOpPkg));
+        mPreviousVibrations.addLast(new VibrationInfo(
+                    vib.mStartTime, vib.mEffect, vib.mUsageHint, vib.mUid, vib.mOpPkg));
     }
 
     @Override // Binder call
@@ -401,97 +403,97 @@
                 android.Manifest.permission.VIBRATE,
                 "cancelVibrate");
 
-        // so wakelock calls will succeed
-        long identity = Binder.clearCallingIdentity();
-        try {
-            synchronized (mVibrations) {
-                final Vibration vib = removeVibrationLocked(token);
-                if (vib == mCurrentVibration) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Canceling vibration.");
-                    }
+        synchronized (mLock) {
+            if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Canceling vibration.");
+                }
+                long ident = Binder.clearCallingIdentity();
+                try {
                     doCancelVibrateLocked();
-                    startNextVibrationLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
                 }
             }
         }
-        finally {
-            Binder.restoreCallingIdentity(identity);
-        }
     }
 
-    private final Runnable mVibrationRunnable = new Runnable() {
+    private final Runnable mVibrationEndRunnable = new Runnable() {
         @Override
         public void run() {
-            synchronized (mVibrations) {
-                doCancelVibrateLocked();
-                startNextVibrationLocked();
-            }
+            onVibrationFinished();
         }
     };
 
-    // Lock held on mVibrations
     private void doCancelVibrateLocked() {
+        mH.removeCallbacks(mVibrationEndRunnable);
         if (mThread != null) {
-            synchronized (mThread) {
-                mThread.mDone = true;
-                mThread.notify();
-            }
+            mThread.cancel();
             mThread = null;
         }
         doVibratorOff();
-        mH.removeCallbacks(mVibrationRunnable);
         reportFinishVibrationLocked();
     }
 
-    // Lock held on mVibrations
-    private void startNextVibrationLocked() {
-        if (mVibrations.size() <= 0) {
-            reportFinishVibrationLocked();
-            mCurrentVibration = null;
-            return;
+    // Callback for whenever the current vibration has finished played out
+    public void onVibrationFinished() {
+        if (DEBUG) {
+            Slog.e(TAG, "Vibration finished, cleaning up");
         }
-        startVibrationLocked(mVibrations.getFirst());
+        synchronized (mLock) {
+            // Make sure the vibration is really done. This also reports that the vibration is
+            // finished.
+            doCancelVibrateLocked();
+        }
     }
 
-    // Lock held on mVibrations
     private void startVibrationLocked(final Vibration vib) {
-        try {
-            if (mLowPowerMode
-                    && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
-                return;
+        if (mLowPowerMode && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, low power mode");
             }
-
-            if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
-                    !shouldVibrateForRingtone()) {
-                return;
-            }
-
-            int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
-                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
-                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
-            }
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                mCurrentVibration = vib;
-            } else {
-                if (mode == AppOpsManager.MODE_ERRORED) {
-                    Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
-                }
-                mH.post(mVibrationRunnable);
-                return;
-            }
-        } catch (RemoteException e) {
+            return;
         }
-        if (vib.mTimeout != 0) {
-            doVibratorOn(vib.mTimeout, vib.mUid, vib.mUsageHint);
-            mH.postDelayed(mVibrationRunnable, vib.mTimeout);
-        } else {
+
+        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
+                !shouldVibrateForRingtone()) {
+            if (DEBUG) {
+                Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+            }
+            return;
+        }
+
+        final int mode = getAppOpMode(vib);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            if (mode == AppOpsManager.MODE_ERRORED) {
+                // We might be getting calls from within system_server, so we don't actually want
+                // to throw a SecurityException here.
+                Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+            }
+            return;
+        }
+        startVibrationInnerLocked(vib);
+    }
+
+    private void startVibrationInnerLocked(Vibration vib) {
+        mCurrentVibration = vib;
+        if (vib.mEffect instanceof VibrationEffect.OneShot) {
+            VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.mEffect;
+            doVibratorOn(oneShot.getTiming(), oneShot.getAmplitude(), vib.mUid, vib.mUsageHint);
+            mH.postDelayed(mVibrationEndRunnable, oneShot.getTiming());
+        } else if (vib.mEffect instanceof VibrationEffect.Waveform) {
             // mThread better be null here. doCancelVibrate should always be
             // called before startNextVibrationLocked or startVibrationLocked.
-            mThread = new VibrateThread(vib);
+            VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.mEffect;
+            mThread = new VibrateThread(waveform, vib.mUid, vib.mUsageHint);
             mThread.start();
+        } else if (vib.mEffect instanceof VibrationEffect.Prebaked) {
+            long timeout = doVibratorPrebakedEffectLocked(vib);
+            if (timeout > 0) {
+                mH.postDelayed(mVibrationEndRunnable, timeout);
+            }
+        } else {
+            Slog.e(TAG, "Unknown vibration type, ignoring");
         }
     }
 
@@ -507,104 +509,115 @@
         }
     }
 
+    private int getAppOpMode(Vibration vib) {
+        int mode;
+        try {
+            mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+                    vib.mUsageHint, vib.mUid, vib.mOpPkg);
+            if (mode == AppOpsManager.MODE_ALLOWED) {
+                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mOpPkg);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to get appop mode for vibration!", e);
+            mode = AppOpsManager.MODE_IGNORED;
+        }
+        return mode;
+    }
+
     private void reportFinishVibrationLocked() {
         if (mCurrentVibration != null) {
             try {
                 mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
                         AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
                         mCurrentVibration.mOpPkg);
-            } catch (RemoteException e) {
-            }
+            } catch (RemoteException e) { }
             mCurrentVibration = null;
         }
     }
 
-    // Lock held on mVibrations
-    private Vibration removeVibrationLocked(IBinder token) {
-        ListIterator<Vibration> iter = mVibrations.listIterator(0);
-        while (iter.hasNext()) {
-            Vibration vib = iter.next();
-            if (vib.mToken == token) {
-                iter.remove();
-                unlinkVibration(vib);
-                return vib;
-            }
-        }
-        // We might be looking for a simple vibration which is only stored in
-        // mCurrentVibration.
-        if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
-            unlinkVibration(mCurrentVibration);
-            return mCurrentVibration;
-        }
-        return null;
-    }
-
     private void unlinkVibration(Vibration vib) {
-        if (vib.mPattern != null) {
-            // If Vibration object has a pattern,
-            // the Vibration object has also been linkedToDeath.
+        if (vib.mEffect instanceof VibrationEffect.Waveform) {
             vib.mToken.unlinkToDeath(vib, 0);
         }
     }
 
-    private void updateInputDeviceVibrators() {
-        synchronized (mVibrations) {
-            doCancelVibrateLocked();
+    private void updateVibrators() {
+        synchronized (mLock) {
+            boolean devicesUpdated = updateInputDeviceVibratorsLocked();
+            boolean lowPowerModeUpdated = updateLowPowerModeLocked();
 
-            synchronized (mInputDeviceVibrators) {
-                mVibrateInputDevicesSetting = false;
-                try {
-                    mVibrateInputDevicesSetting = Settings.System.getIntForUser(
-                            mContext.getContentResolver(),
-                            Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
-                } catch (SettingNotFoundException snfe) {
-                }
+            if (devicesUpdated || lowPowerModeUpdated) {
+                // If the state changes out from under us then just reset.
+                doCancelVibrateLocked();
+            }
+        }
+    }
 
-                mLowPowerMode = mPowerManagerInternal
-                        .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+    private boolean updateInputDeviceVibratorsLocked() {
+        boolean changed = false;
+        boolean vibrateInputDevices = false;
+        try {
+            vibrateInputDevices = Settings.System.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+        } catch (SettingNotFoundException snfe) {
+        }
+        if (vibrateInputDevices != mVibrateInputDevicesSetting) {
+            changed = true;
+            mVibrateInputDevicesSetting = vibrateInputDevices;
+        }
 
-                if (mVibrateInputDevicesSetting) {
-                    if (!mInputDeviceListenerRegistered) {
-                        mInputDeviceListenerRegistered = true;
-                        mIm.registerInputDeviceListener(this, mH);
-                    }
-                } else {
-                    if (mInputDeviceListenerRegistered) {
-                        mInputDeviceListenerRegistered = false;
-                        mIm.unregisterInputDeviceListener(this);
-                    }
-                }
+        if (mVibrateInputDevicesSetting) {
+            if (!mInputDeviceListenerRegistered) {
+                mInputDeviceListenerRegistered = true;
+                mIm.registerInputDeviceListener(this, mH);
+            }
+        } else {
+            if (mInputDeviceListenerRegistered) {
+                mInputDeviceListenerRegistered = false;
+                mIm.unregisterInputDeviceListener(this);
+            }
+        }
 
-                mInputDeviceVibrators.clear();
-                if (mVibrateInputDevicesSetting) {
-                    int[] ids = mIm.getInputDeviceIds();
-                    for (int i = 0; i < ids.length; i++) {
-                        InputDevice device = mIm.getInputDevice(ids[i]);
-                        Vibrator vibrator = device.getVibrator();
-                        if (vibrator.hasVibrator()) {
-                            mInputDeviceVibrators.add(vibrator);
-                        }
-                    }
+        mInputDeviceVibrators.clear();
+        if (mVibrateInputDevicesSetting) {
+            int[] ids = mIm.getInputDeviceIds();
+            for (int i = 0; i < ids.length; i++) {
+                InputDevice device = mIm.getInputDevice(ids[i]);
+                Vibrator vibrator = device.getVibrator();
+                if (vibrator.hasVibrator()) {
+                    mInputDeviceVibrators.add(vibrator);
                 }
             }
-
-            startNextVibrationLocked();
+            return true;
         }
+        return changed;
+    }
+
+    private boolean updateLowPowerModeLocked() {
+        boolean lowPowerMode = mPowerManagerInternal
+                .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
+        if (lowPowerMode != mLowPowerMode) {
+            mLowPowerMode = lowPowerMode;
+            return true;
+        }
+        return false;
     }
 
     @Override
     public void onInputDeviceAdded(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     @Override
     public void onInputDeviceChanged(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     @Override
     public void onInputDeviceRemoved(int deviceId) {
-        updateInputDeviceVibrators();
+        updateVibrators();
     }
 
     private boolean doVibratorExists() {
@@ -619,41 +632,44 @@
         return vibratorExists();
     }
 
-    private void doVibratorOn(long millis, int uid, int usageHint) {
+    private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
         synchronized (mInputDeviceVibrators) {
+            if (amplitude == VibrationEffect.DEFAULT_AMPLITUDE) {
+                amplitude = mDefaultVibrationAmplitude;
+            }
             if (DEBUG) {
-                Slog.d(TAG, "Turning vibrator on for " + millis + " ms.");
+                Slog.d(TAG, "Turning vibrator on for " + millis + " ms" +
+                        " with amplitude " + amplitude + ".");
             }
-            try {
-                mBatteryStatsService.noteVibratorOn(uid, millis);
-                mCurVibUid = uid;
-            } catch (RemoteException e) {
-            }
+            noteVibratorOnLocked(uid, millis);
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
-                final AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usageHint)
-                        .build();
+                final AudioAttributes attributes =
+                        new AudioAttributes.Builder().setUsage(usageHint).build();
                 for (int i = 0; i < vibratorCount; i++) {
                     mInputDeviceVibrators.get(i).vibrate(millis, attributes);
                 }
             } else {
+                // Note: ordering is important here! Many haptic drivers will reset their amplitude
+                // when enabled, so we always have to enable frst, then set the amplitude.
                 vibratorOn(millis);
+                doVibratorSetAmplitude(amplitude);
             }
         }
     }
 
+    private void doVibratorSetAmplitude(int amplitude) {
+        if (mSupportsAmplitudeControl) {
+            vibratorSetAmplitude(amplitude);
+        }
+    }
+
     private void doVibratorOff() {
         synchronized (mInputDeviceVibrators) {
             if (DEBUG) {
                 Slog.d(TAG, "Turning vibrator off.");
             }
-            if (mCurVibUid >= 0) {
-                try {
-                    mBatteryStatsService.noteVibratorOff(mCurVibUid);
-                } catch (RemoteException e) {
-                }
-                mCurVibUid = -1;
-            }
+            noteVibratorOffLocked();
             final int vibratorCount = mInputDeviceVibrators.size();
             if (vibratorCount != 0) {
                 for (int i = 0; i < vibratorCount; i++) {
@@ -665,86 +681,175 @@
         }
     }
 
-    private class VibrateThread extends Thread {
-        final Vibration mVibration;
-        boolean mDone;
+    private long doVibratorPrebakedEffectLocked(Vibration vib) {
+        synchronized (mInputDeviceVibrators) {
+            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.mEffect;
+            // Input devices don't support prebaked effect, so skip trying it with them.
+            final int vibratorCount = mInputDeviceVibrators.size();
+            if (vibratorCount == 0) {
+                long timeout = vibratorPerformEffect(prebaked.getId(), EffectStrength.MEDIUM);
+                if (timeout > 0) {
+                    noteVibratorOnLocked(vib.mUid, timeout);
+                    return timeout;
+                }
+            }
+            final int id = prebaked.getId();
+            if (id < 0 || id >= mFallbackEffects.length) {
+                Slog.w(TAG, "Failed to play prebaked effect, no fallback");
+                return 0;
+            }
+            VibrationEffect effect = mFallbackEffects[id];
+            Vibration fallbackVib =
+                    new Vibration(vib.mToken, effect, vib.mUsageHint, vib.mUid, vib.mOpPkg);
+            startVibrationInnerLocked(fallbackVib);
+        }
+        return 0;
+    }
 
-        VibrateThread(Vibration vib) {
-            mVibration = vib;
-            mTmpWorkSource.set(vib.mUid);
+    private void noteVibratorOnLocked(int uid, long millis) {
+        try {
+            mBatteryStatsService.noteVibratorOn(uid, millis);
+            mCurVibUid = uid;
+        } catch (RemoteException e) {
+        }
+    }
+
+    private void noteVibratorOffLocked() {
+        if (mCurVibUid >= 0) {
+            try {
+                mBatteryStatsService.noteVibratorOff(mCurVibUid);
+            } catch (RemoteException e) { }
+            mCurVibUid = -1;
+        }
+    }
+
+    private class VibrateThread extends Thread {
+        private final VibrationEffect.Waveform mWaveform;
+        private final int mUid;
+        private final int mUsageHint;
+
+        private boolean mForceStop;
+
+        VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+            mWaveform = waveform;
+            mUid = uid;
+            mUsageHint = usageHint;
+            mTmpWorkSource.set(uid);
             mWakeLock.setWorkSource(mTmpWorkSource);
-            mWakeLock.acquire();
         }
 
-        private void delay(long duration) {
+        private long delayLocked(long duration) {
+            long durationRemaining = duration;
             if (duration > 0) {
-                long bedtime = duration + SystemClock.uptimeMillis();
+                final long bedtime = duration + SystemClock.uptimeMillis();
                 do {
                     try {
-                        this.wait(duration);
+                        this.wait(durationRemaining);
                     }
-                    catch (InterruptedException e) {
-                    }
-                    if (mDone) {
+                    catch (InterruptedException e) { }
+                    if (mForceStop) {
                         break;
                     }
-                    duration = bedtime - SystemClock.uptimeMillis();
-                } while (duration > 0);
+                    durationRemaining = bedtime - SystemClock.uptimeMillis();
+                } while (durationRemaining > 0);
+                return duration - durationRemaining;
             }
+            return 0;
         }
 
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
-            synchronized (this) {
-                final long[] pattern = mVibration.mPattern;
-                final int len = pattern.length;
-                final int repeat = mVibration.mRepeat;
-                final int uid = mVibration.mUid;
-                final int usageHint = mVibration.mUsageHint;
-                int index = 0;
-                long duration = 0;
-
-                while (!mDone) {
-                    // add off-time duration to any accumulated on-time duration
-                    if (index < len) {
-                        duration += pattern[index++];
-                    }
-
-                    // sleep until it is time to start the vibrator
-                    delay(duration);
-                    if (mDone) {
-                        break;
-                    }
-
-                    if (index < len) {
-                        // read on-time duration and start the vibrator
-                        // duration is saved for delay() at top of loop
-                        duration = pattern[index++];
-                        if (duration > 0) {
-                            VibratorService.this.doVibratorOn(duration, uid, usageHint);
-                        }
-                    } else {
-                        if (repeat < 0) {
-                            break;
-                        } else {
-                            index = repeat;
-                            duration = 0;
-                        }
-                    }
+            mWakeLock.acquire();
+            try {
+                boolean finished = playWaveform();
+                if (finished) {
+                    onVibrationFinished();
                 }
+            } finally {
                 mWakeLock.release();
             }
-            synchronized (mVibrations) {
-                if (mThread == this) {
-                    mThread = null;
+        }
+
+        /**
+         * Play the waveform.
+         *
+         * @return true if it finished naturally, false otherwise (e.g. it was canceled).
+         */
+        public boolean playWaveform() {
+            synchronized (this) {
+                final long[] timings = mWaveform.getTimings();
+                final int[] amplitudes = mWaveform.getAmplitudes();
+                final int len = timings.length;
+                final int repeat = mWaveform.getRepeatIndex();
+
+                int index = 0;
+                long onDuration = 0;
+                while (!mForceStop) {
+                    if (index < len) {
+                        final int amplitude = amplitudes[index];
+                        final long duration = timings[index++];
+                        if (duration <= 0) {
+                            continue;
+                        }
+                        if (amplitude != 0) {
+                            if (onDuration <= 0) {
+                                // Telling the vibrator to start multiple times usually causes
+                                // effects to feel "choppy" because the motor resets at every on
+                                // command.  Instead we figure out how long our next "on" period is
+                                // going to be, tell the motor to stay on for the full duration,
+                                // and then wake up to change the amplitude at the appropriate
+                                // intervals.
+                                onDuration =
+                                        getTotalOnDuration(timings, amplitudes, index - 1, repeat);
+                                doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+                            } else {
+                                doVibratorSetAmplitude(amplitude);
+                            }
+                        }
+
+                        long waitTime = delayLocked(duration);
+                        if (amplitude != 0) {
+                            onDuration -= waitTime;
+                        }
+                    } else if (repeat < 0) {
+                        break;
+                    } else {
+                        index = repeat;
+                    }
                 }
-                if (!mDone) {
-                    // If this vibration finished naturally, start the next
-                    // vibration.
-                    unlinkVibration(mVibration);
-                    startNextVibrationLocked();
+                return !mForceStop;
+            }
+        }
+
+        public void cancel() {
+            synchronized (this) {
+                mThread.mForceStop = true;
+                mThread.notify();
+            }
+        }
+
+        /**
+         * Get the duration the vibrator will be on starting at startIndex until the next time it's
+         * off.
+         */
+        private long getTotalOnDuration(
+                long[] timings, int[] amplitudes, int startIndex, int repeatIndex) {
+            int i = startIndex;
+            long timing = 0;
+            while(amplitudes[i] != 0) {
+                timing += timings[i++];
+                if (i >= timings.length) {
+                    if (repeatIndex >= 0) {
+                        i = repeatIndex;
+                    } else {
+                        break;
+                    }
+                }
+                if (i == startIndex) {
+                    return 1000;
                 }
             }
+            return timing;
         }
     }
 
@@ -752,7 +857,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                synchronized (mVibrations) {
+                synchronized (mLock) {
                     // When the system is entering a non-interactive state, we want
                     // to cancel vibrations in case a misbehaving app has abandoned
                     // them.  However it may happen that the system is currently playing
@@ -762,16 +867,6 @@
                             && !mCurrentVibration.isSystemHapticFeedback()) {
                         doCancelVibrateLocked();
                     }
-
-                    // Clear all remaining vibrations.
-                    Iterator<Vibration> it = mVibrations.iterator();
-                    while (it.hasNext()) {
-                        Vibration vibration = it.next();
-                        if (vibration != mCurrentVibration) {
-                            unlinkVibration(vibration);
-                            it.remove();
-                        }
-                    }
                 }
             }
         }
@@ -788,7 +883,7 @@
             return;
         }
         pw.println("Previous vibrations:");
-        synchronized (mVibrations) {
+        synchronized (mLock) {
             for (VibrationInfo info : mPreviousVibrations) {
                 pw.print("  ");
                 pw.println(info.toString());
@@ -830,7 +925,10 @@
             if (description == null) {
                 description = "Shell command";
             }
-            vibrate(Binder.getCallingUid(), description, duration, AudioAttributes.USAGE_UNKNOWN,
+
+            VibrationEffect effect =
+                    VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
+            vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
                     mToken);
             return 0;
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 46552e2..5956923 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5482,37 +5482,61 @@
         return tracesFile;
     }
 
+    public static class DumpStackFileObserver extends FileObserver {
+        // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
+        private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+        static final int TRACE_DUMP_TIMEOUT_SECONDS = TRACE_DUMP_TIMEOUT_MS / 1000;
+
+        private final String mTracesPath;
+        private boolean mClosed;
+
+        public DumpStackFileObserver(String tracesPath) {
+            super(tracesPath, FileObserver.CLOSE_WRITE);
+            mTracesPath = tracesPath;
+        }
+
+        @Override
+        public synchronized void onEvent(int event, String path) {
+            mClosed = true;
+            notify();
+        }
+
+        public void dumpWithTimeout(int pid) {
+            Process.sendSignal(pid, Process.SIGNAL_QUIT);
+            synchronized (this) {
+                try {
+                    wait(TRACE_DUMP_TIMEOUT_MS); // Wait for traces file to be closed.
+                } catch (InterruptedException e) {
+                    Slog.wtf(TAG, e);
+                }
+            }
+            if (!mClosed) {
+                Slog.w(TAG, "Didn't see close of " + mTracesPath + " for pid " + pid +
+                       ". Attempting native stack collection.");
+                Debug.dumpNativeBacktraceToFileTimeout(pid, mTracesPath, TRACE_DUMP_TIMEOUT_SECONDS);
+            }
+            mClosed = false;
+        }
+    }
+
     private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
             ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
         // Use a FileObserver to detect when traces finish writing.
         // The order of traces is considered important to maintain for legibility.
-        final boolean[] closed = new boolean[1];
-        FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
-            @Override
-            public synchronized void onEvent(int event, String path) { closed[0] = true; notify(); }
-        };
-
+        DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
         try {
             observer.startWatching();
 
             // First collect all of the stacks of the most important pids.
             if (firstPids != null) {
-                try {
-                    int num = firstPids.size();
-                    for (int i = 0; i < num; i++) {
-                        synchronized (observer) {
-                            if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
-                                    + firstPids.get(i));
-                            final long sime = SystemClock.elapsedRealtime();
-                            Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
-                            observer.wait(1000);  // Wait for write-close, give up after 1 sec
-                            if (!closed[0]) Slog.w(TAG, "Didn't see close of " + tracesPath);
-                            if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
-                                    + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
-                        }
-                    }
-                } catch (InterruptedException e) {
-                    Slog.wtf(TAG, e);
+                int num = firstPids.size();
+                for (int i = 0; i < num; i++) {
+                    if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+                            + firstPids.get(i));
+                    final long sime = SystemClock.elapsedRealtime();
+                    observer.dumpWithTimeout(firstPids.get(i));
+                    if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
+                            + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                 }
             }
 
@@ -5524,7 +5548,8 @@
                         if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
                         final long sime = SystemClock.elapsedRealtime();
 
-                        Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
+                        Debug.dumpNativeBacktraceToFileTimeout(
+                                pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
                         if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
                                 + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                     }
@@ -5551,19 +5576,12 @@
                     ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
                     if (lastPids.indexOfKey(stats.pid) >= 0) {
                         numProcs++;
-                        try {
-                            synchronized (observer) {
-                                if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
-                                        + stats.pid);
-                                final long stime = SystemClock.elapsedRealtime();
-                                Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
-                                observer.wait(1000);  // Wait for write-close, give up after 1 sec
-                                if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
-                                        + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
-                            }
-                        } catch (InterruptedException e) {
-                            Slog.wtf(TAG, e);
-                        }
+                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
+                                + stats.pid);
+                        final long stime = SystemClock.elapsedRealtime();
+                        observer.dumpWithTimeout(stats.pid);
+                        if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
+                                + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
                     } else if (DEBUG_ANR) {
                         Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
                                 + stats.pid);
@@ -10028,7 +10046,7 @@
     }
 
     @Override
-    public TaskSnapshot getTaskSnapshot(int taskId) {
+    public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
         enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -10042,7 +10060,7 @@
                 }
             }
             // Don't call this while holding the lock as this operation might hit the disk.
-            return task.getSnapshot();
+            return task.getSnapshot(reducedResolution);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 99fe418..13c8865 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -737,11 +737,11 @@
     /**
      * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    TaskSnapshot getSnapshot() {
+    TaskSnapshot getSnapshot(boolean reducedResolution) {
 
         // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more
         // synchronized between AM and WM.
-        return mService.mWindowManager.getTaskSnapshot(taskId, userId);
+        return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution);
     }
 
     void touchActiveTime() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3dcc5d9..06f0144 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -105,6 +105,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
@@ -3097,8 +3098,17 @@
         if (mIsTelevision && (new Notification.TvExtender(notification)).getChannel() != null) {
             channelId = (new Notification.TvExtender(notification)).getChannel();
         }
-        final NotificationChannel channel =  mRankingHelper.getNotificationChannelWithFallback(pkg,
+        final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
                 notificationUid, channelId, false /* includeDeleted */);
+        if (channel == null) {
+            // STOPSHIP TODO: remove before release - should always throw without a valid channel.
+            if (channelId == null) {
+                Log.e(TAG, "Cannot post notification without channel ID when targeting O "
+                        + " - notification=" + notification);
+                return;
+            }
+            throw new IllegalArgumentException("No Channel found for notification=" + notification);
+        }
         final StatusBarNotification n = new StatusBarNotification(
                 pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                 user, null, System.currentTimeMillis());
@@ -3613,9 +3623,12 @@
         // notifying app does not have the VIBRATE permission.
         long identity = Binder.clearCallingIdentity();
         try {
-            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), vibration,
-                    ((record.getNotification().flags & Notification.FLAG_INSISTENT) != 0)
-                            ? 0: -1, record.getAudioAttributes());
+            final boolean insistent =
+                (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+            final VibrationEffect effect = VibrationEffect.createWaveform(
+                    vibration, insistent ? 0 : -1 /*repeatIndex*/);
+            mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(),
+                    effect, record.getAudioAttributes());
             return true;
         } finally{
             Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 6a00722..e13df19 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,6 @@
     void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
     void updateNotificationChannelFromAssistant(String pkg, int uid, NotificationChannel channel);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
-    NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 7feaf5a..ce79465 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -213,7 +213,11 @@
                             }
                         }
 
-                        clampDefaultChannel(r);
+                        try {
+                            deleteDefaultChannelIfNeeded(r);
+                        } catch (NameNotFoundException e) {
+                            Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+                        }
                     }
                 }
             }
@@ -247,60 +251,94 @@
             r.priority = priority;
             r.visibility = visibility;
             r.showBadge = showBadge;
-            createDefaultChannelIfMissing(r);
+
+            try {
+                createDefaultChannelIfNeeded(r);
+            } catch (NameNotFoundException e) {
+                Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+            }
+
             if (r.uid == Record.UNKNOWN_UID) {
                 mRestoredWithoutUids.put(pkg, r);
             } else {
                 mRecords.put(key, r);
             }
-            clampDefaultChannel(r);
         }
         return r;
     }
 
-    // Clamp the importance level of the default channel for apps targeting the new SDK version,
-    // unless the user has already changed the importance.
-    private void clampDefaultChannel(Record r) {
-        try {
-            if (r.uid != Record.UNKNOWN_UID) {
-                int userId = UserHandle.getUserId(r.uid);
-                final ApplicationInfo applicationInfo =
-                        mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
-                if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
-                    final NotificationChannel defaultChannel =
-                            r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
-                    if ((defaultChannel.getUserLockedFields()
-                            & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
-                        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-                        updateConfig();
-                    }
-                }
-            }
-        } catch (NameNotFoundException e) {
-            // oh well.
+    private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
+        final int userId = UserHandle.getUserId(r.uid);
+        final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+        if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
+            // Pre-O apps should have it.
+            return true;
         }
+
+        // STOPSHIP TODO: remove before release - O+ apps should never have a default channel.
+        // But for now, leave the default channel until an app has created its first channel.
+        boolean hasCreatedAChannel = false;
+        final int size = r.channels.size();
+        for (int i = 0; i < size; i++) {
+            final NotificationChannel notificationChannel = r.channels.valueAt(i);
+            if (notificationChannel != null &&
+                    notificationChannel.getId() != NotificationChannel.DEFAULT_CHANNEL_ID) {
+                hasCreatedAChannel = true;
+                break;
+            }
+        }
+        if (!hasCreatedAChannel) {
+            return true;
+        }
+
+        // Otherwise, should not have the default channel.
+        return false;
     }
 
-    private void createDefaultChannelIfMissing(Record r) {
+    private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
         if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            NotificationChannel channel;
-            channel = new NotificationChannel(
-                    NotificationChannel.DEFAULT_CHANNEL_ID,
-                    mContext.getString(R.string.default_notification_channel_label),
-                    r.importance);
-            channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
-            channel.setLockscreenVisibility(r.visibility);
-            if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
-                channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-            }
-            if (r.priority != DEFAULT_PRIORITY) {
-                channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
-            }
-            if (r.visibility != DEFAULT_VISIBILITY) {
-                channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
-            }
-            r.channels.put(channel.getId(), channel);
+            // Not present
+            return;
         }
+
+        if (shouldHaveDefaultChannel(r)) {
+            // Keep the default channel until upgraded.
+            return;
+        }
+
+        // Remove Default Channel.
+        r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+    }
+
+    private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
+        if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            // Already exists
+            return;
+        }
+
+        if (!shouldHaveDefaultChannel(r)) {
+            // Keep the default channel until upgraded.
+            return;
+        }
+
+        // Create Default Channel
+        NotificationChannel channel;
+        channel = new NotificationChannel(
+                NotificationChannel.DEFAULT_CHANNEL_ID,
+                mContext.getString(R.string.default_notification_channel_label),
+                r.importance);
+        channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+        channel.setLockscreenVisibility(r.visibility);
+        if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+        }
+        if (r.priority != DEFAULT_PRIORITY) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+        }
+        if (r.visibility != DEFAULT_VISIBILITY) {
+            channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+        }
+        r.channels.put(channel.getId(), channel);
     }
 
     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
@@ -619,21 +657,6 @@
     }
 
     @Override
-    public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
-            String channelId, boolean includeDeleted) {
-        Record r = getOrCreateRecord(pkg, uid);
-        if (channelId == null) {
-            channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
-        }
-        NotificationChannel channel = r.channels.get(channelId);
-        if (channel != null && (includeDeleted || !channel.isDeleted())) {
-            return channel;
-        } else {
-            return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
-        }
-    }
-
-    @Override
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Preconditions.checkNotNull(pkg);
@@ -1057,10 +1080,9 @@
                     Record fullRecord = getRecord(pkg,
                             mPm.getPackageUidAsUser(pkg, changeUserId));
                     if (fullRecord != null) {
-                        clampDefaultChannel(fullRecord);
+                        deleteDefaultChannelIfNeeded(fullRecord);
                     }
-                } catch (NameNotFoundException e) {
-                }
+                } catch (NameNotFoundException e) {}
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7eb4df8..9ce7fef 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -177,6 +177,7 @@
 import android.os.UEventObserver;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
@@ -7527,17 +7528,35 @@
         if (hapticsDisabled && !always) {
             return false;
         }
-        long[] pattern = null;
+
+        VibrationEffect effect = getVibrationEffect(effectId);
+        if (effect == null) {
+            return false;
+        }
+
+        int owningUid;
+        String owningPackage;
+        if (win != null) {
+            owningUid = win.getOwningUid();
+            owningPackage = win.getOwningPackage();
+        } else {
+            owningUid = android.os.Process.myUid();
+            owningPackage = mContext.getOpPackageName();
+        }
+        mVibrator.vibrate(owningUid, owningPackage, effect, VIBRATION_ATTRIBUTES);
+        return true;
+    }
+
+    private VibrationEffect getVibrationEffect(int effectId) {
+        long[] pattern;
         switch (effectId) {
+            case HapticFeedbackConstants.VIRTUAL_KEY:
+                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.LONG_PRESS:
                 pattern = mLongPressVibePattern;
                 break;
-            case HapticFeedbackConstants.VIRTUAL_KEY:
-                pattern = mVirtualKeyVibePattern;
-                break;
             case HapticFeedbackConstants.KEYBOARD_TAP:
-                pattern = mKeyboardTapVibePattern;
-                break;
+                return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
             case HapticFeedbackConstants.CLOCK_TICK:
                 pattern = mClockTickVibePattern;
                 break;
@@ -7554,25 +7573,15 @@
                 pattern = mContextClickVibePattern;
                 break;
             default:
-                return false;
-        }
-        int owningUid;
-        String owningPackage;
-        if (win != null) {
-            owningUid = win.getOwningUid();
-            owningPackage = win.getOwningPackage();
-        } else {
-            owningUid = android.os.Process.myUid();
-            owningPackage = mContext.getOpPackageName();
+                return null;
         }
         if (pattern.length == 1) {
             // One-shot vibration
-            mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
+            return VibrationEffect.createOneShot(pattern[0], VibrationEffect.DEFAULT_AMPLITUDE);
         } else {
             // Pattern vibration
-            mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
+            return VibrationEffect.createWaveform(pattern, -1);
         }
-        return true;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index b90a82a..bd38be4 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -535,7 +535,7 @@
     private boolean createSnapshot() {
         final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
                 mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
-            false /* restoreFromDisk */);
+                false /* restoreFromDisk */, false /* reducedResolution */);
 
         if (snapshot == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 4028336..1ec0201 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -61,7 +61,8 @@
     /**
      * If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW MANAGER LOCK!
      */
-    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
 
         synchronized (mService.mWindowMap) {
             // Try the running cache.
@@ -81,19 +82,23 @@
         if (!restoreFromDisk) {
             return null;
         }
-        return tryRestoreFromDisk(taskId, userId);
+        return tryRestoreFromDisk(taskId, userId, reducedResolution);
     }
 
     /**
      * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId) {
-        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId);
+    private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean reducedResolution) {
+        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, reducedResolution);
         if (snapshot == null) {
             return null;
         }
-        synchronized (mService.mWindowMap) {
-            mRetrievalCache.put(taskId, snapshot);
+
+        // Only cache non-reduced snapshots.
+        if (!reducedResolution) {
+            synchronized (mService.mWindowMap) {
+                mRetrievalCache.put(taskId, snapshot);
+            }
         }
         return snapshot;
     }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5995bba..469a8a7 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -106,8 +106,9 @@
      * Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO HOLD THE WINDOW
      * MANAGER LOCK WHEN CALLING THIS METHOD!
      */
-    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk) {
-        return mCache.getSnapshot(taskId, userId, restoreFromDisk);
+    @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
+            boolean reducedResolution) {
+        return mCache.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution);
     }
 
     /**
@@ -130,7 +131,7 @@
             return null;
         }
         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
-                top.findMainWindow().mStableInsets);
+                top.findMainWindow().mStableInsets, false /* reduced */, 1f /* scale */);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index 4340822..ec21d25 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.TaskSnapshotPersister.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -59,11 +60,14 @@
      *
      * @param taskId The id of the task to load.
      * @param userId The id of the user the task belonged to.
+     * @param reducedResolution Whether to load a reduced resolution version of the snapshot.
      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
      */
-    TaskSnapshot loadTask(int taskId, int userId) {
+    TaskSnapshot loadTask(int taskId, int userId, boolean reducedResolution) {
         final File protoFile = mPersister.getProtoFile(taskId, userId);
-        final File bitmapFile = mPersister.getBitmapFile(taskId, userId);
+        final File bitmapFile = reducedResolution
+                ? mPersister.getReducedResolutionBitmapFile(taskId, userId)
+                : mPersister.getBitmapFile(taskId, userId);
         if (!protoFile.exists() || !bitmapFile.exists()) {
             return null;
         }
@@ -84,7 +88,8 @@
                 return null;
             }
             return new TaskSnapshot(buffer, proto.orientation,
-                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom));
+                    new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+                    reducedResolution, reducedResolution ? REDUCED_SCALE : 1f);
         } catch (IOException e) {
             Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
             return null;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3a06c38..f2a92df 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.graphics.Bitmap.CompressFormat.*;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -47,9 +48,12 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
     private static final String SNAPSHOTS_DIRNAME = "snapshots";
+    private static final String REDUCED_POSTFIX = "_reduced";
+    static final float REDUCED_SCALE = 0.5f;
     private static final long DELAY_MS = 100;
+    private static final int QUALITY = 95;
     private static final String PROTO_EXTENSION = ".proto";
-    private static final String BITMAP_EXTENSION = ".png";
+    private static final String BITMAP_EXTENSION = ".jpg";
 
     @GuardedBy("mLock")
     private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
@@ -152,6 +156,10 @@
         return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
     }
 
+    File getReducedResolutionBitmapFile(int taskId, int userId) {
+        return new File(getDirectory(userId), taskId + REDUCED_POSTFIX + BITMAP_EXTENSION);
+    }
+
     private boolean createDirectory(int userId) {
         final File dir = getDirectory(userId);
         return dir.exists() || dir.mkdirs();
@@ -160,8 +168,10 @@
     private void deleteSnapshot(int taskId, int userId) {
         final File protoFile = getProtoFile(taskId, userId);
         final File bitmapFile = getBitmapFile(taskId, userId);
+        final File bitmapReducedFile = getReducedResolutionBitmapFile(taskId, userId);
         protoFile.delete();
         bitmapFile.delete();
+        bitmapReducedFile.delete();
     }
 
     interface DirectoryResolver {
@@ -254,13 +264,20 @@
 
         boolean writeBuffer() {
             final File file = getBitmapFile(mTaskId, mUserId);
+            final File reducedFile = getReducedResolutionBitmapFile(mTaskId, mUserId);
             final Bitmap bitmap = Bitmap.createHardwareBitmap(mSnapshot.getSnapshot());
+            final Bitmap reduced = Bitmap.createScaledBitmap(bitmap,
+                    (int) (bitmap.getWidth() * REDUCED_SCALE),
+                    (int) (bitmap.getHeight() * REDUCED_SCALE), true /* filter */);
             try {
                 FileOutputStream fos = new FileOutputStream(file);
-                bitmap.compress(CompressFormat.PNG, 0 /* quality */, fos);
+                bitmap.compress(JPEG, QUALITY, fos);
                 fos.close();
+                FileOutputStream reducedFos = new FileOutputStream(reducedFile);
+                reduced.compress(JPEG, QUALITY, reducedFos);
+                reducedFos.close();
             } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+                Slog.e(TAG, "Unable to open " + file + " or " + reducedFile +" for persisting.", e);
                 return false;
             }
             return true;
@@ -325,8 +342,12 @@
             if (end == -1) {
                 return -1;
             }
+            String name = fileName.substring(0, end);
+            if (name.endsWith(REDUCED_POSTFIX)) {
+                name = name.substring(0, name.length() - REDUCED_POSTFIX.length());
+            }
             try {
-                return Integer.parseInt(fileName.substring(0, end));
+                return Integer.parseInt(name);
             } catch (NumberFormatException e) {
                 return -1;
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5edb82c..5844b0b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3621,8 +3621,9 @@
         return true;
     }
 
-    public TaskSnapshot getTaskSnapshot(int taskId, int userId) {
-        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */);
+    public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) {
+        return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */,
+                reducedResolution);
     }
 
     /**
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index 50bae794..76ce890 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -27,8 +27,12 @@
 #include <utils/Log.h>
 #include <hardware/vibrator.h>
 
+#include <inttypes.h>
 #include <stdio.h>
 
+using android::hardware::Return;
+using android::hardware::vibrator::V1_0::Effect;
+using android::hardware::vibrator::V1_0::EffectStrength;
 using android::hardware::vibrator::V1_0::IVibrator;
 using android::hardware::vibrator::V1_0::Status;
 
@@ -59,8 +63,8 @@
 {
     if (mHal != nullptr) {
         Status retStatus = mHal->on(timeout_ms);
-        if (retStatus == Status::ERR) {
-            ALOGE("vibratorOn command failed.");
+        if (retStatus != Status::OK) {
+            ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
         }
     } else {
         ALOGW("Tried to vibrate but there is no vibrator device.");
@@ -71,19 +75,68 @@
 {
     if (mHal != nullptr) {
         Status retStatus = mHal->off();
-        if (retStatus == Status::ERR) {
-            ALOGE("vibratorOff command failed.");
+        if (retStatus != Status::OK) {
+            ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));
         }
     } else {
         ALOGW("Tried to stop vibrating but there is no vibrator device.");
     }
 }
 
+static jlong vibratorSupportsAmplitudeControl(JNIEnv*, jobject) {
+    if (mHal != nullptr) {
+        return mHal->supportsAmplitudeControl();
+    } else {
+        ALOGW("Unable to get max vibration amplitude, there is no vibrator device.");
+    }
+    return false;
+}
+
+static void vibratorSetAmplitude(JNIEnv*, jobject, jint amplitude) {
+    if (mHal != nullptr) {
+        Status status = mHal->setAmplitude(static_cast<uint32_t>(amplitude));
+        if (status != Status::OK) {
+            ALOGE("Failed to set vibrator amplitude (%" PRIu32 ").",
+                    static_cast<uint32_t>(status));
+        }
+    } else {
+        ALOGW("Unable to set vibration amplitude, there is no vibrator device.");
+    }
+}
+
+static jlong vibratorPerformEffect(JNIEnv*, jobject, jlong effect, jint strength) {
+    if (mHal != nullptr) {
+        Status status;
+        uint32_t lengthMs;
+        mHal->perform(static_cast<Effect>(effect), static_cast<EffectStrength>(strength),
+                [&status, &lengthMs](Status retStatus, uint32_t retLengthMs) {
+                    status = retStatus;
+                    lengthMs = retLengthMs;
+                });
+        if (status == Status::OK) {
+            return lengthMs;
+        } else if (status != Status::UNSUPPORTED_OPERATION) {
+            // Don't warn on UNSUPPORTED_OPERATION, that's a normal even and just means the motor
+            // doesn't have a pre-defined waveform to perform for it, so we should just fall back
+            // to the framework waveforms.
+            ALOGE("Failed to perform haptic effect: effect=%" PRId64 ", strength=%" PRId32
+                    ", error=%" PRIu32 ").", static_cast<int64_t>(effect),
+                    static_cast<int32_t>(strength), static_cast<uint32_t>(status));
+        }
+    } else {
+        ALOGW("Unable to perform haptic effect, there is no vibrator device.");
+    }
+    return -1;
+}
+
 static const JNINativeMethod method_table[] = {
     { "vibratorExists", "()Z", (void*)vibratorExists },
     { "vibratorInit", "()V", (void*)vibratorInit },
     { "vibratorOn", "(J)V", (void*)vibratorOn },
-    { "vibratorOff", "()V", (void*)vibratorOff }
+    { "vibratorOff", "()V", (void*)vibratorOff },
+    { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},
+    { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},
+    { "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
 };
 
 int register_android_server_VibratorService(JNIEnv *env)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 2413561..5c3a37a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -76,15 +76,28 @@
      * Internally how often should the monitor poll the security logs from logd.
      */
     private static final long POLLING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
+    /**
+     * Overlap between two subsequent log requests, required to avoid losing out of order events.
+     */
+    private static final long OVERLAP_NANOS = TimeUnit.SECONDS.toNanos(3);
+
 
     @GuardedBy("mLock")
     private Thread mMonitorThread = null;
     @GuardedBy("mLock")
-    private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<SecurityEvent>();
+    private ArrayList<SecurityEvent> mPendingLogs = new ArrayList<>();
     @GuardedBy("mLock")
     private boolean mAllowedToRetrieve = false;
 
     /**
+     * Last events fetched from log to check for overlap between batches. We can leave it empty if
+     * we are sure there will be no overlap anymore, e.g. when we get empty batch.
+     */
+    private final ArrayList<SecurityEvent> mLastEvents = new ArrayList<>();
+    /** Timestamp of the very last event, -1 means request from the beginning of time. */
+    private long mLastEventNanos = -1;
+
+    /**
      * When DO will be allowed to retrieve the log, in milliseconds since boot (as per
      * {@link SystemClock#elapsedRealtime()}). After that it will mark the time to retry broadcast.
      */
@@ -98,7 +111,7 @@
         mLock.lock();
         try {
             if (mMonitorThread == null) {
-                mPendingLogs = new ArrayList<SecurityEvent>();
+                mPendingLogs = new ArrayList<>();
                 mAllowedToRetrieve = false;
                 mNextAllowedRetrievalTimeMillis = -1;
                 mPaused = false;
@@ -123,7 +136,7 @@
                     Log.e(TAG, "Interrupted while waiting for thread to stop", e);
                 }
                 // Reset state and clear buffer
-                mPendingLogs = new ArrayList<SecurityEvent>();
+                mPendingLogs = new ArrayList<>();
                 mAllowedToRetrieve = false;
                 mNextAllowedRetrievalTimeMillis = -1;
                 mPaused = false;
@@ -181,7 +194,7 @@
     void discardLogs() {
         mLock.lock();
         mAllowedToRetrieve = false;
-        mPendingLogs = new ArrayList<SecurityEvent>();
+        mPendingLogs = new ArrayList<>();
         mLock.unlock();
         Slog.i(TAG, "Discarded all logs.");
     }
@@ -198,7 +211,7 @@
                 mNextAllowedRetrievalTimeMillis = SystemClock.elapsedRealtime()
                         + RATE_LIMIT_INTERVAL_MILLISECONDS;
                 List<SecurityEvent> result = mPendingLogs;
-                mPendingLogs = new ArrayList<SecurityEvent>();
+                mPendingLogs = new ArrayList<>();
                 return result;
             } else {
                 return null;
@@ -208,45 +221,141 @@
         }
     }
 
+    /**
+     * Requests the next (or the first) batch of events from the log with appropriate timestamp.
+     */
+    private void getNextBatch(ArrayList<SecurityEvent> newLogs)
+            throws IOException, InterruptedException {
+        if (mLastEventNanos < 0) {
+            // Non-blocking read that returns all logs immediately.
+            if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
+            SecurityLog.readEvents(newLogs);
+        } else {
+            // If we have last events from the previous batch, request log events with time overlap
+            // with previously retrieved messages to avoid losing events due to reordering in logd.
+            final long startNanos = mLastEvents.isEmpty()
+                    ? mLastEventNanos : Math.max(0, mLastEventNanos - OVERLAP_NANOS);
+            if (DEBUG) Slog.d(TAG, "SecurityLog.readEventsSince: " + startNanos);
+            // Non-blocking read that returns all logs with timestamps >= startNanos immediately.
+            SecurityLog.readEventsSince(startNanos, newLogs);
+        }
+
+        // Sometimes events may be reordered in logd due to simultaneous readers and writers. In
+        // this case, we have to sort it to make overlap checking work. This is very unlikely.
+        for (int i = 0; i < newLogs.size() - 1; i++) {
+            if (newLogs.get(i).getTimeNanos() > newLogs.get(i+1).getTimeNanos()) {
+                if (DEBUG) Slog.d(TAG, "Got out of order events, sorting.");
+                // Sort using comparator that compares timestamps.
+                newLogs.sort((e1, e2) -> Long.signum(e1.getTimeNanos() - e2.getTimeNanos()));
+                break;
+            }
+        }
+
+        if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
+    }
+
+    /**
+     * Save the last events for overlap checking with the next batch.
+     */
+    private void saveLastEvents(ArrayList<SecurityEvent> newLogs) {
+        mLastEvents.clear();
+        if (newLogs.isEmpty()) {
+            // This can happen if no events were logged yet or the buffer got cleared. In this case
+            // we aren't going to have any overlap next time, leave mLastEvents events empty.
+            return;
+        }
+
+        // Save the last timestamp.
+        mLastEventNanos = newLogs.get(newLogs.size() - 1).getTimeNanos();
+        // Position of the earliest event that has to be saved. Start from the penultimate event,
+        // going backward.
+        int pos = newLogs.size() - 2;
+        while (pos >= 0 && mLastEventNanos - newLogs.get(pos).getTimeNanos() < OVERLAP_NANOS) {
+            pos--;
+        }
+        // We either run past the start of the list or encountered an event that is too old to keep.
+        pos++;
+        mLastEvents.addAll(newLogs.subList(pos, newLogs.size()));
+        if (DEBUG) Slog.d(TAG, mLastEvents.size() + " events saved for overlap check");
+    }
+
+    /**
+     * Merges a new batch into already fetched logs and deals with overlapping and out of order
+     * events.
+     */
+    @GuardedBy("mLock")
+    private void mergeBatchLocked(final ArrayList<SecurityEvent> newLogs) {
+        // Reserve capacity so that copying doesn't occur.
+        mPendingLogs.ensureCapacity(mPendingLogs.size() + newLogs.size());
+        // Run through the first events of the batch to check if there is an overlap with previous
+        // batch and if so, skip overlapping events. Events are sorted by timestamp, so we can
+        // compare it in linear time by advancing two pointers, one for each batch.
+        int curPos = 0;
+        int lastPos = 0;
+        // For the first batch mLastEvents will be empty, so no iterations will happen.
+        while (lastPos < mLastEvents.size() && curPos < newLogs.size()) {
+            final SecurityEvent curEvent = newLogs.get(curPos);
+            final long currentNanos = curEvent.getTimeNanos();
+            if (currentNanos > mLastEventNanos) {
+                // We got past the last event of the last batch, no overlap possible anymore.
+                break;
+            }
+            final SecurityEvent lastEvent = mLastEvents.get(lastPos);
+            final long lastNanos = lastEvent.getTimeNanos();
+            if (lastNanos > currentNanos) {
+                // New event older than the last we've seen so far, must be due to reordering.
+                if (DEBUG) Slog.d(TAG, "New event in the overlap: " + currentNanos);
+                mPendingLogs.add(curEvent);
+                curPos++;
+            } else if (lastNanos < currentNanos) {
+                if (DEBUG) Slog.d(TAG, "Event disappeared from the overlap: " + lastNanos);
+                lastPos++;
+            } else {
+                // Two events have the same timestamp, check if they are the same.
+                if (lastEvent.equals(curEvent)) {
+                    // Actual overlap, just skip the event.
+                    if (DEBUG) Slog.d(TAG, "Skipped dup event with timestamp: " + lastNanos);
+                } else {
+                    // Wow, what a coincidence, or probably the clock is too coarse.
+                    mPendingLogs.add(curEvent);
+                    if (DEBUG) Slog.d(TAG, "Event timestamp collision: " + lastNanos);
+                }
+                lastPos++;
+                curPos++;
+            }
+        }
+        // Save the rest of the new batch.
+        mPendingLogs.addAll(newLogs.subList(curPos, newLogs.size()));
+
+        if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
+            // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL.
+            mPendingLogs = new ArrayList<>(mPendingLogs.subList(
+                    mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
+                    mPendingLogs.size()));
+            Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
+        }
+        if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging");
+    }
+
     @Override
     public void run() {
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 
-        ArrayList<SecurityEvent> logs = new ArrayList<SecurityEvent>();
-        // The timestamp of the latest log entry that has been read, in nanoseconds
-        long lastLogTimestampNanos = -1;
+        ArrayList<SecurityEvent> newLogs = new ArrayList<>();
         while (!Thread.currentThread().isInterrupted()) {
             try {
                 Thread.sleep(POLLING_INTERVAL_MILLISECONDS);
+                getNextBatch(newLogs);
 
-                if (lastLogTimestampNanos < 0) {
-                    // Non-blocking read that returns all logs immediately.
-                    if (DEBUG) Slog.d(TAG, "SecurityLog.readEvents");
-                    SecurityLog.readEvents(logs);
-                } else {
-                    if (DEBUG) Slog.d(TAG,
-                            "SecurityLog.readEventsSince: " + lastLogTimestampNanos);
-                    // Non-blocking read that returns all logs >= the  timestamp immediately.
-                    SecurityLog.readEventsSince(lastLogTimestampNanos + 1, logs);
+                mLock.lockInterruptibly();
+                try {
+                    mergeBatchLocked(newLogs);
+                } finally {
+                    mLock.unlock();
                 }
-                if (!logs.isEmpty()) {
-                    if (DEBUG) Slog.d(TAG, "processing new logs. Events: " + logs.size());
-                    mLock.lockInterruptibly();
-                    try {
-                        mPendingLogs.addAll(logs);
-                        if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) {
-                            // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL
-                            mPendingLogs = new ArrayList<SecurityEvent>(mPendingLogs.subList(
-                                    mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2),
-                                    mPendingLogs.size()));
-                            Slog.i(TAG, "Pending logs buffer full. Discarding old logs.");
-                        }
-                    } finally {
-                        mLock.unlock();
-                    }
-                    lastLogTimestampNanos = logs.get(logs.size() - 1).getTimeNanos();
-                    logs.clear();
-                }
+
+                saveLastEvents(newLogs);
+                newLogs.clear();
                 notifyDeviceOwnerIfNeeded();
             } catch (IOException e) {
                 Log.e(TAG, "Failed to read security log", e);
@@ -256,6 +365,15 @@
                 break;
             }
         }
+
+        // Discard previous batch info.
+        mLastEvents.clear();
+        if (mLastEventNanos != -1) {
+            // Make sure we don't read old events if logging is re-enabled. Since mLastEvents is
+            // empty, the next request will be done without overlap, so it is enough to add 1 ns.
+            mLastEventNanos += 1;
+        }
+
         Slog.i(TAG, "MonitorThread exit.");
     }
 
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 15f7557..e285669 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -39,12 +39,14 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.Vibrator;
+import android.os.VibrationEffect;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
@@ -53,6 +55,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -78,6 +81,9 @@
     private int mPid = 2000;
     private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
 
+    private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
+    private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
+
     private static final long[] CUSTOM_VIBRATION = new long[] {
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
             300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
@@ -90,7 +96,9 @@
     private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
     private static final int CUSTOM_LIGHT_ON = 10000;
     private static final int CUSTOM_LIGHT_OFF = 10000;
-    private static final long[] FALLBACK_VIBRATION = new long[] {100, 100, 100};
+    private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
+    private static final VibrationEffect FALLBACK_VIBRATION =
+            VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
 
     @Before
     public void setUp() {
@@ -108,7 +116,7 @@
         mService.setHandler(mHandler);
         mService.setLights(mLight);
         mService.setScreenOn(false);
-        mService.setFallbackVibrationPattern(FALLBACK_VIBRATION);
+        mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
     }
 
     //
@@ -272,18 +280,18 @@
     }
 
     private void verifyNeverVibrate() {
-        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
-                anyInt(), (AudioAttributes) anyObject());
+        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
+                (AudioAttributes) anyObject());
     }
 
     private void verifyVibrate() {
-        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
-                eq(-1), (AudioAttributes) anyObject());
+        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
+                (AudioAttributes) anyObject());
     }
 
     private void verifyVibrateLooped() {
-        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
-                eq(0), (AudioAttributes) anyObject());
+        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
+                (AudioAttributes) anyObject());
     }
 
     private void verifyStopVibrate() {
@@ -485,8 +493,10 @@
 
         mService.buzzBeepBlinkLocked(r);
 
-       verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(r.getVibration()),
-                    eq(-1), (AudioAttributes) anyObject());
+        VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
+
+        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(effect),
+                    (AudioAttributes) anyObject());
     }
 
     @Test
@@ -501,7 +511,7 @@
         mService.buzzBeepBlinkLocked(r);
 
         verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
-                eq(-1), (AudioAttributes) anyObject());
+                (AudioAttributes) anyObject());
         verify(mRingtonePlayer, never()).playAsync
                 (anyObject(), anyObject(), anyBoolean(), anyObject());
     }
@@ -667,4 +677,27 @@
         mService.buzzBeepBlinkLocked(s);
         verifyStopVibrate();
     }
+
+    static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
+        private final int mRepeatIndex;
+
+        VibrateRepeatMatcher(int repeatIndex) {
+            mRepeatIndex = repeatIndex;
+        }
+
+        @Override
+        public boolean matches(VibrationEffect actual) {
+            if (actual instanceof VibrationEffect.Waveform &&
+                    ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
+                return true;
+            }
+            // All non-waveform effects are essentially one shots.
+            return mRepeatIndex == -1;
+        }
+
+        @Override
+        public String toString() {
+            return "repeatIndex=" + mRepeatIndex;
+        }
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index b7b3617..ab83b9d 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -59,21 +59,23 @@
 
 public class NotificationManagerServiceTest {
     private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
-    private final String pkg = "com.android.server.notification";
+    private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
     private final int uid = Binder.getCallingUid();
     private NotificationManagerService mNotificationManagerService;
     private INotificationManager mBinderService;
     private IPackageManager mPackageManager = mock(IPackageManager.class);
-    final PackageManager mPackageManagerClient = mock(PackageManager.class);
-    private Context mContext;
+    private final PackageManager mPackageManagerClient = mock(PackageManager.class);
+    private Context mContext = InstrumentationRegistry.getTargetContext();
+    private final String PKG = mContext.getPackageName();
     private HandlerThread mThread;
-    final RankingHelper mRankingHelper = mock(RankingHelper.class);
+    private final RankingHelper mRankingHelper = mock(RankingHelper.class);
+    private NotificationChannel mTestNotificationChannel = new NotificationChannel(
+            TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
 
     @Before
     @Test
     @UiThreadTest
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
         mNotificationManagerService = new NotificationManagerService(mContext);
 
         // MockPackageManager - default returns ApplicationInfo with matching calling UID
@@ -93,13 +95,16 @@
                 mock(NotificationManagerService.NotificationListeners.class);
         when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn(
                 mockNotificationListeners.new ManagedServiceInfo(null,
-                        new ComponentName(pkg, "test_class"), uid, true, null, 0));
+                        new ComponentName(PKG, "test_class"), uid, true, null, 0));
 
         mNotificationManagerService.init(mThread.getLooper(), mPackageManager,
                 mPackageManagerClient, mockLightsManager, mockNotificationListeners);
 
         // Tests call directly into the Binder.
         mBinderService = mNotificationManagerService.getBinderService();
+
+        mBinderService.createNotificationChannels(
+                PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
     }
 
     public void waitForIdle() throws Exception {
@@ -127,7 +132,7 @@
     private NotificationRecord generateNotificationRecord(NotificationChannel channel,
             Notification.TvExtender extender) {
         if (channel == null) {
-            channel = new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
+            channel = mTestNotificationChannel;
         }
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
@@ -135,8 +140,7 @@
         if (extender != null) {
             nb.extend(extender);
         }
-        StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
-                mContext.getPackageName(), 1, "tag", uid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", uid, 0,
                 nb.build(), new UserHandle(uid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -256,38 +260,38 @@
     @Test
     @UiThreadTest
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(mContext.getPackageName());
+                mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifs.length);
     }
 
     @Test
     @UiThreadTest
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
-        mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(mContext.getPackageName());
+                mBinderService.getActiveNotifications(PKG);
         assertEquals(0, notifs.length);
     }
 
     @Test
     @UiThreadTest
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
         waitForIdle();
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), new int[1], 0);
-        mBinderService.cancelNotificationWithTag(mContext.getPackageName(), "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, "tag", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
-                mBinderService.getActiveNotifications(mContext.getPackageName());
+                mBinderService.getActiveNotifications(PKG);
         assertEquals(0, notifs.length);
     }
 
@@ -295,7 +299,7 @@
     @UiThreadTest
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
         mBinderService.cancelNotificationsFromListener(null, null);
         waitForIdle();
@@ -308,9 +312,9 @@
     @UiThreadTest
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
-        mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -322,9 +326,9 @@
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
-        mBinderService.cancelAllNotifications(sbn.getPackageName(), sbn.getUserId());
+        mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
@@ -336,7 +340,7 @@
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
         mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
         waitForIdle();
@@ -349,7 +353,7 @@
     @UiThreadTest
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], sbn.getUserId());
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
         waitForIdle();
@@ -362,7 +366,7 @@
     @UiThreadTest
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), "opPkg", "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), new int[1], UserHandle.USER_ALL);
         // Null pkg is how we signal a user switch.
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -377,14 +381,14 @@
     public void testTvExtenderChannelOverride_onTv() throws Exception {
         mNotificationManagerService.setIsTelevision(true);
         mNotificationManagerService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelWithFallback(
+        when(mRankingHelper.getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
                         new NotificationChannel("foo", "foo", NotificationManager.IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
-        verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
+        verify(mRankingHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean());
     }
 
@@ -393,14 +397,14 @@
     public void testTvExtenderChannelOverride_notOnTv() throws Exception {
         mNotificationManagerService.setIsTelevision(false);
         mNotificationManagerService.setRankingHelper(mRankingHelper);
-        when(mRankingHelper.getNotificationChannelWithFallback(
+        when(mRankingHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
-                new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH));
+                mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannel("foo");
-        mBinderService.enqueueNotificationWithTag(mContext.getPackageName(), "opPkg", "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null, tv).getNotification(), new int[1], 0);
-        verify(mRankingHelper, times(1)).getNotificationChannelWithFallback(
-                anyString(), anyInt(), eq("id"), anyBoolean());
+        verify(mRankingHelper, times(1)).getNotificationChannel(
+                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
     }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index fab8434..5a94018 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -78,12 +78,15 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RankingHelperTest {
-    @Mock
-    NotificationUsageStats mUsageStats;
-    @Mock
-    RankingHandler handler;
-    @Mock
-    PackageManager mPm;
+    private static final String PKG = "com.android.server.notification";
+    private static final int UID = 0;
+    private static final String UPDATED_PKG = "updatedPkg";
+    private static final int UID2 = 1111111;
+    private static final String TEST_CHANNEL_ID = "test_channel_id";
+
+    @Mock NotificationUsageStats mUsageStats;
+    @Mock RankingHandler mHandler;
+    @Mock PackageManager mPm;
 
     private Notification mNotiGroupGSortA;
     private Notification mNotiGroupGSortB;
@@ -96,11 +99,6 @@
     private NotificationRecord mRecordNoGroup2;
     private NotificationRecord mRecordNoGroupSortA;
     private RankingHelper mHelper;
-    private final String pkg = "com.android.server.notification";
-    private final int uid = 0;
-    private final String pkg2 = "pkg2";
-    private final int uid2 = 1111111;
-    private static final String TEST_CHANNEL_ID = "test_channel_id";
     private AudioAttributes mAudioAttributes;
 
     private Context getContext() {
@@ -108,12 +106,12 @@
     }
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         UserHandle user = UserHandle.ALL;
 
-        mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
-                new String[]{ImportanceExtractor.class.getName()});
+        mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
+                new String[] {ImportanceExtractor.class.getName()});
 
         mNotiGroupGSortA = new Notification.Builder(getContext(), TEST_CHANNEL_ID)
                 .setContentTitle("A")
@@ -170,12 +168,9 @@
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
         final ApplicationInfo upgrade = new ApplicationInfo();
         upgrade.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
-        try {
-            when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
-            when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
-            when(mPm.getPackageUidAsUser(eq(pkg), anyInt())).thenReturn(uid);
-        } catch (PackageManager.NameNotFoundException e) {
-        }
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+        when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+        when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
     }
 
     private NotificationChannel getDefaultChannel() {
@@ -202,6 +197,14 @@
         return baos;
     }
 
+    private void loadStreamXml(ByteArrayOutputStream stream) throws Exception {
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
+                null);
+        parser.nextTag();
+        mHelper.readXml(parser, false);
+    }
+
     private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
         assertEquals(expected.getId(), actual.getId());
         assertEquals(expected.getName(), actual.getName());
@@ -289,34 +292,28 @@
         channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
         channel2.setLightColor(Color.BLUE);
 
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, false);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false);
 
-        mHelper.setShowBadge(pkg, uid, true);
-        mHelper.setShowBadge(pkg2, uid2, false);
+        mHelper.setShowBadge(PKG, UID, true);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
                 channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
 
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
+        loadStreamXml(baos);
 
-        assertFalse(mHelper.canShowBadge(pkg2, uid2));
-        assertTrue(mHelper.canShowBadge(pkg, uid));
-        assertEquals(channel1, mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
+        assertTrue(mHelper.canShowBadge(PKG, UID));
+        assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
         compareChannels(channel2,
-                mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
         assertNotNull(mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false));
 
         List<NotificationChannelGroup> actualGroups =
-                mHelper.getNotificationChannelGroups(pkg, uid, false).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
         boolean foundNcg = false;
         for (NotificationChannelGroup actual : actualGroups) {
             if (ncg.getId().equals(actual.getId())) {
@@ -350,19 +347,19 @@
                 new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
         channel3.setGroup(ncg.getId());
 
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, false);
-        mHelper.createNotificationChannel(pkg, uid, channel3, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel1.getId());
-        mHelper.deleteNotificationChannelGroup(pkg, uid, ncg.getId());
-        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+        mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+        mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, true, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
                 channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
-        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
 
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -370,11 +367,11 @@
         parser.nextTag();
         mHelper.readXml(parser, true);
 
-        assertNull(mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false));
-        assertNull(mHelper.getNotificationChannel(pkg, uid, channel3.getId(), false));
-        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), pkg, uid));
-        //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), pkg, uid));
-        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel2.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+        assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+        //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
     }
 
     @Test
@@ -382,19 +379,15 @@
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_DEFAULT);
 
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid,false, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
 
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
+        loadStreamXml(baos);
 
-        final NotificationChannel updated = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
         assertFalse(updated.canBypassDnd());
         assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
@@ -405,34 +398,30 @@
     public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        defaultChannel.setImportance(IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(pkg, uid, defaultChannel);
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+        mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
 
-        ByteArrayOutputStream baos = writeXmlAndPurge(pkg, uid, false, channel1.getId(),
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
 
-        XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
-                null);
-        parser.nextTag();
-        mHelper.readXml(parser, false);
+        loadStreamXml(baos);
 
-        assertEquals(IMPORTANCE_LOW, mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+        assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
     }
 
     @Test
     public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
         final String preupgradeXml = "<ranking version=\"1\">\n"
-                + "<package name=\"" + pkg + "\" importance=\""
-                + NotificationManager.IMPORTANCE_HIGH
+                + "<package name=\"" + PKG
+                + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
                 + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
-                + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
-                + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+                + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+                + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
                 + Notification.VISIBILITY_PRIVATE + "\" />\n"
                 + "</ranking>";
         XmlPullParser parser = Xml.newPullParser();
@@ -441,30 +430,69 @@
         parser.nextTag();
         mHelper.readXml(parser, false);
 
-        final NotificationChannel updated1 = mHelper.getNotificationChannel(
-                pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        final NotificationChannel updated1 =
+            mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
         assertTrue(updated1.canBypassDnd());
         assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
         assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
                 | NotificationChannel.USER_LOCKED_PRIORITY
-                | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
+                | NotificationChannel.USER_LOCKED_VISIBILITY,
+                updated1.getUserLockedFields());
 
-        final NotificationChannel updated2 = mHelper.getNotificationChannel(
-                pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-        // clamped
-        assertEquals(IMPORTANCE_LOW, updated2.getImportance());
-        assertFalse(updated2.canBypassDnd());
-        assertEquals(Notification.VISIBILITY_PRIVATE, updated2.getLockscreenVisibility());
-        assertEquals(NotificationChannel.USER_LOCKED_VISIBILITY, updated2.getUserLockedFields());
+        // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+        // No Default Channel created for updated packages
+        // assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+        //         NotificationChannel.DEFAULT_CHANNEL_ID, false));
+        assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+    }
+
+    @Test
+    public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+        final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+                PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        assertTrue(defaultChannel != null);
+        ByteArrayOutputStream baos =
+                writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+        // Load package at higher sdk.
+        final ApplicationInfo upgraded = new ApplicationInfo();
+        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+        loadStreamXml(baos);
+
+        // STOPSHIP - this should be reversed after the STOPSHIP is removed in the tested code.
+        // Default Channel should be gone.
+        // assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+        //         NotificationChannel.DEFAULT_CHANNEL_ID, false));
+        assertTrue(mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false) != null);
+    }
+
+    @Test
+    public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+                NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+        // Load package at higher sdk.
+        final ApplicationInfo upgraded = new ApplicationInfo();
+        upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+        when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+        loadStreamXml(baos);
+
+        // Default Channel should be gone.
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+                NotificationChannel.DEFAULT_CHANNEL_ID, false));
     }
 
     @Test
     public void testCreateChannel_blocked() throws Exception {
-        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE);
+        mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_NONE);
 
-        mHelper.createNotificationChannel(pkg, uid,
-                new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true);
+        mHelper.createNotificationChannel(PKG, UID,
+                new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true);
     }
 
     @Test
@@ -474,16 +502,16 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -494,17 +522,17 @@
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
         channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -515,7 +543,7 @@
         channel.enableLights(false);
         channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
@@ -523,10 +551,10 @@
         channel2.enableVibration(true);
         channel2.setVibrationPattern(new long[]{100});
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -537,17 +565,17 @@
         channel.enableLights(false);
         channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.enableLights(true);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -558,17 +586,17 @@
         channel.setBypassDnd(true);
         channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setBypassDnd(false);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -579,17 +607,17 @@
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -599,16 +627,16 @@
         channel.setShowBadge(true);
         channel.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         final NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
         channel2.setShowBadge(false);
 
-        mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
+        mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel2);
 
         // no fields should be changed
-        assertEquals(channel, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
@@ -621,7 +649,7 @@
         channel.setBypassDnd(true);
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, false);
+        mHelper.createNotificationChannel(PKG, UID, channel, false);
 
         // same id, try to update all fields
         final NotificationChannel channel2 =
@@ -631,17 +659,15 @@
         channel2.setBypassDnd(false);
         channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
 
-        mHelper.updateNotificationChannel(pkg, uid, channel2);
+        mHelper.updateNotificationChannel(PKG, UID, channel2);
 
         // all fields should be changed
-        assertEquals(channel2, mHelper.getNotificationChannel(pkg, uid, channel.getId(), false));
+        assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
     }
 
     @Test
-    public void testGetChannelWithFallback() throws Exception {
-        NotificationChannel channel =
-                mHelper.getNotificationChannelWithFallback(pkg, uid, "garbage", false);
-        assertEquals(NotificationChannel.DEFAULT_CHANNEL_ID, channel.getId());
+    public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+        assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
     }
 
     @Test
@@ -659,10 +685,10 @@
         }
         channel.lockFields(lockMask);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
         NotificationChannel savedChannel =
-                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
 
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -686,10 +712,10 @@
         }
         channel.lockFields(lockMask);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
         NotificationChannel savedChannel =
-                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
 
         assertEquals(channel.getName(), savedChannel.getName());
         assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
@@ -709,16 +735,16 @@
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[]{100, 67, 145, 156});
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         // Does not return deleted channel
         NotificationChannel response =
-                mHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+                mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
         assertNull(response);
 
         // Returns deleted channel
-        response = mHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+        response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
         compareChannels(channel, response);
         assertTrue(response.isDeleted());
     }
@@ -738,14 +764,14 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
         channelMap.put(channel2.getId(), channel2);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         // Returns only non-deleted channels
         List<NotificationChannel> channels =
-                mHelper.getNotificationChannels(pkg, uid, false).getList();
+                mHelper.getNotificationChannels(PKG, UID, false).getList();
         assertEquals(2, channels.size());   // Default channel + non-deleted channel
         for (NotificationChannel nc : channels) {
             if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -754,7 +780,7 @@
         }
 
         // Returns deleted channels too
-        channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+        channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
         assertEquals(3, channels.size());               // Includes default channel
         for (NotificationChannel nc : channels) {
             if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -771,35 +797,35 @@
                 new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
         NotificationChannel channel3 =
                 new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, true);
-        mHelper.createNotificationChannel(pkg, uid, channel3, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
-        mHelper.deleteNotificationChannel(pkg, uid, channel3.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
 
-        assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid));
-        assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2));
+        assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+        assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
     }
 
     @Test
     public void testUpdateDeletedChannels() throws Exception {
         NotificationChannel channel =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
         try {
-            mHelper.updateNotificationChannel(pkg, uid, channel);
+            mHelper.updateNotificationChannel(PKG, UID, channel);
             fail("Updated deleted channel");
         } catch (IllegalArgumentException e) {
             // :)
         }
 
         try {
-            mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel);
+            mHelper.updateNotificationChannelFromAssistant(PKG, UID, channel);
             fail("Updated deleted channel");
         } catch (IllegalArgumentException e) {
             // :)
@@ -813,24 +839,24 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.setVibrationPattern(vibration);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
-        mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
 
         NotificationChannel newChannel = new NotificationChannel(
                 channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
         newChannel.setVibrationPattern(new long[]{100});
 
-        mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+        mHelper.createNotificationChannel(PKG, UID, newChannel, true);
 
         // No long deleted, using old settings
         compareChannels(channel,
-                mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
     }
 
     @Test
     public void testCreateChannel_defaultChannelId() throws Exception {
         try {
-            mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel(
+            mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
                     NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true);
             fail("Allowed to create default channel");
         } catch (IllegalArgumentException e) {
@@ -845,26 +871,26 @@
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel.setVibrationPattern(vibration);
 
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
 
         NotificationChannel newChannel = new NotificationChannel(
                 channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
         newChannel.setVibrationPattern(new long[]{100});
 
-        mHelper.createNotificationChannel(pkg, uid, newChannel, true);
+        mHelper.createNotificationChannel(PKG, UID, newChannel, true);
 
         // Old settings not overridden
         compareChannels(channel,
-                mHelper.getNotificationChannel(pkg, uid, newChannel.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
     }
 
     @Test
     public void testCreateChannel_addMissingSound() throws Exception {
         final NotificationChannel channel =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
         assertNotNull(mHelper.getNotificationChannel(
-                pkg, uid, channel.getId(), false).getSound());
+                PKG, UID, channel.getId(), false).getSound());
     }
 
     @Test
@@ -873,9 +899,9 @@
         final NotificationChannel channel = new NotificationChannel("id2", "name2",
                  NotificationManager.IMPORTANCE_DEFAULT);
         channel.setSound(sound, mAudioAttributes);
-        mHelper.createNotificationChannel(pkg, uid, channel, true);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
         assertEquals(sound, mHelper.getNotificationChannel(
-                pkg, uid, channel.getId(), false).getSound());
+                PKG, UID, channel.getId(), false).getSound());
     }
 
     @Test
@@ -885,13 +911,13 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
 
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.createNotificationChannel(pkg, uid, channel2, false);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, false);
 
-        mHelper.permanentlyDeleteNotificationChannels(pkg, uid);
+        mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
 
         // Only default channel remains
-        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
     }
 
     @Test
@@ -907,28 +933,28 @@
                 new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
         groupedAndDeleted.setGroup("totally");
 
-        mHelper.createNotificationChannelGroup(pkg, uid, notDeleted, true);
-        mHelper.createNotificationChannelGroup(pkg, uid, deleted, true);
-        mHelper.createNotificationChannel(pkg, uid, nonGroupedNonDeletedChannel, true);
-        mHelper.createNotificationChannel(pkg, uid, groupedAndDeleted, true);
-        mHelper.createNotificationChannel(pkg, uid, groupedButNotDeleted, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+        mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true);
+        mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true);
+        mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true);
 
-        mHelper.deleteNotificationChannelGroup(pkg, uid, deleted.getId());
+        mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
 
-        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), pkg, uid));
-        assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), pkg, uid));
+        assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+        assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
 
-        assertNull(mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), false));
+        assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
         compareChannels(groupedAndDeleted,
-                mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), true));
+                mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
 
         compareChannels(groupedButNotDeleted,
-                mHelper.getNotificationChannel(pkg, uid, groupedButNotDeleted.getId(), false));
+                mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
         compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
-                pkg, uid, nonGroupedNonDeletedChannel.getId(), false));
+                PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
 
         // notDeleted
-        assertEquals(1, mHelper.getNotificationChannelGroups(pkg, uid).size());
+        assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
     }
 
     @Test
@@ -936,52 +962,52 @@
         // Deleted
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(0, mHelper.getNotificationChannels(pkg, uid, true).getList().size());
+        assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
 
         // Not deleted
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
-        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
-        assertEquals(2, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+        mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+        assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
     }
 
     @Test
     public void testOnPackageChanged_packageRemoval_importance() throws Exception {
-        mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_HIGH);
+        mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
     }
 
     @Test
     public void testOnPackageChanged_packageRemoval_groups() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid});
+        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid).size());
+        assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
     }
 
     @Test
     public void testRecordDefaults() throws Exception {
-        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(pkg, uid));
-        assertEquals(true, mHelper.canShowBadge(pkg, uid));
-        assertEquals(1, mHelper.getNotificationChannels(pkg, uid, false).getList().size());
+        assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+        assertEquals(true, mHelper.canShowBadge(PKG, UID));
+        assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
     }
 
     @Test
     public void testCreateGroup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
-        assertEquals(ncg, mHelper.getNotificationChannelGroups(pkg, uid).iterator().next());
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+        assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
     }
 
     @Test
@@ -990,7 +1016,7 @@
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup("garbage");
         try {
-            mHelper.createNotificationChannel(pkg, uid, channel1, true);
+            mHelper.createNotificationChannel(PKG, UID, channel1, true);
             fail("Created a channel with a bad group");
         } catch (IllegalArgumentException e) {
         }
@@ -999,45 +1025,45 @@
     @Test
     public void testCannotCreateChannel_goodGroup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
 
         assertEquals(ncg.getId(),
-                mHelper.getNotificationChannel(pkg, uid, channel1.getId(), false).getGroup());
+                mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
     }
 
     @Test
     public void testGetChannelGroups() throws Exception {
         NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
-        mHelper.createNotificationChannelGroup(pkg, uid, unused, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
         NotificationChannel channel1a =
                 new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1a.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1a, true);
+        mHelper.createNotificationChannel(PKG, UID, channel1a, true);
 
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel2.setGroup(ncg2.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel2, true);
+        mHelper.createNotificationChannel(PKG, UID, channel2, true);
 
         NotificationChannel channel3 =
                 new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, channel3, true);
+        mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
         List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
         assertEquals(3, actual.size());
         for (NotificationChannelGroup group : actual) {
             if (group.getId() == null) {
@@ -1063,19 +1089,19 @@
     @Test
     public void testGetChannelGroups_noSideEffects() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
-        mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+        mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
 
         NotificationChannel channel1 =
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
-        mHelper.createNotificationChannel(pkg, uid, channel1, true);
-        mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+        mHelper.createNotificationChannel(PKG, UID, channel1, true);
+        mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
 
         channel1.setImportance(IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(pkg, uid, channel1);
+        mHelper.updateNotificationChannel(PKG, UID, channel1);
 
         List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
 
         assertEquals(2, actual.size());
         for (NotificationChannelGroup group : actual) {
@@ -1088,14 +1114,14 @@
     @Test
     public void testCreateChannel_updateName() throws Exception {
         NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
-        mHelper.createNotificationChannel(pkg, uid, nc, true);
-        NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+        mHelper.createNotificationChannel(PKG, UID, nc, true);
+        NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
         assertEquals("hello", actual.getName());
 
         nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
-        mHelper.createNotificationChannel(pkg, uid, nc, true);
+        mHelper.createNotificationChannel(PKG, UID, nc, true);
 
-        actual = mHelper.getNotificationChannel(pkg, uid, "id", false);
+        actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
         assertEquals("goodbye", actual.getName());
         assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
     }
@@ -1115,7 +1141,7 @@
             String pkgName = "pkg" + i;
             int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
             for (int j = 0; j < numChannels; j++) {
-                mHelper.createNotificationChannel(pkgName, uid,
+                mHelper.createNotificationChannel(pkgName, UID,
                         new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true);
             }
             expectedChannels.put(pkgName, numChannels);
@@ -1123,7 +1149,7 @@
 
         // delete the first channel of the first package
         String pkg = expectedChannels.keyAt(0);
-        mHelper.deleteNotificationChannel("pkg" + 0, uid, "0");
+        mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
         // dump should not include deleted channels
         int count = expectedChannels.get(pkg);
         expectedChannels.put(pkg, count - 1);
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index 921e0e3..b5826f0 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -132,9 +132,10 @@
         sWm.mDisplayEnabled = true;
 
         // Create an app window with token on a display.
-        final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
+        final DisplayContent defaultDisplayContent = sWm.getDefaultDisplayContentLocked();
+        final TaskStack stack = createTaskStackOnDisplay(defaultDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
-        final TestAppWindowToken appWindowToken = new TestAppWindowToken(sDisplayContent);
+        final TestAppWindowToken appWindowToken = new TestAppWindowToken(defaultDisplayContent);
         task.addChild(appWindowToken, 0);
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
                 TYPE_BASE_APPLICATION);
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f1fcba3..290f69a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -66,10 +66,10 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onAppRemoved(window.mAppToken);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -77,12 +77,12 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onAppDied(window.mAppToken);
 
         // Should still be in the retrieval cache.
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
 
         // Trash retrieval cache.
         for (int i = 0; i < 20; i++) {
@@ -92,7 +92,7 @@
 
         // Should not be in cache anymore
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -100,10 +100,27 @@
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
         mCache.onTaskRemoved(window.getTask().mTaskId);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
+    }
+
+    @Test
+    public void testReduced_notCached() throws Exception {
+        final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+        mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
+        mPersister.waitForQueueEmpty();
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
+
+        // Load it from disk
+        assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                true /* restoreFromDisk */, true /* reducedResolution */));
+
+        // Make sure it's not in the cache now.
+        assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 
     @Test
@@ -112,14 +129,14 @@
         mPersister.persistSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId, createSnapshot());
         mPersister.waitForQueueEmpty();
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
 
         // Load it from disk
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                true /* restoreFromDisk */));
+                true /* restoreFromDisk */, false /* reducedResolution */));
 
         // Make sure it's in the cache now.
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, sWm.mCurrentUserId,
-                false /* restoreFromDisk */));
+                false /* restoreFromDisk */, false /* reducedResolution */));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index dc008b5..4121447 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -26,6 +26,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.os.Debug;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.MediumTest;
@@ -50,7 +51,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBase {
 
-    private static final String TEST_USER_NAME = "TaskSnapshotPersisterTest User";
     private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
 
     @Test
@@ -58,9 +58,10 @@
         mPersister.persistSnapshot(1 , sTestUserId, createSnapshot());
         mPersister.waitForQueueEmpty();
         final File[] files = new File[] { new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png") };
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg")};
         assertTrueForFiles(files, File::exists, " must exist");
-        final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId);
+        final TaskSnapshot snapshot = mLoader.loadTask(1, sTestUserId, false /* reduced */);
         assertNotNull(snapshot);
         assertEquals(TEST_INSETS, snapshot.getContentInsets());
         assertNotNull(snapshot.getSnapshot());
@@ -79,7 +80,8 @@
         mPersister.onTaskRemovedFromRecents(1, sTestUserId);
         mPersister.waitForQueueEmpty();
         assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.proto").exists());
-        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.png").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1.jpg").exists());
+        assertFalse(new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg").exists());
     }
 
     /**
@@ -105,9 +107,10 @@
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
-        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.png"));
+        assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.jpg"));
         assertEquals(12, removeObsoleteFilesQueueItem.getTaskId("12.proto"));
-        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.png"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1.jpg"));
+        assertEquals(1, removeObsoleteFilesQueueItem.getTaskId("1_reduced.jpg"));
     }
 
     @Test
@@ -120,10 +123,12 @@
         mPersister.waitForQueueEmpty();
         final File[] existsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png") };
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg") };
         final File[] nonExistsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/2.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/2.png") };
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
         assertTrueForFiles(existsFiles, File::exists, " must exist");
         assertTrueForFiles(nonExistsFiles, file -> !file.exists(), " must not exist");
     }
@@ -138,9 +143,11 @@
         mPersister.waitForQueueEmpty();
         final File[] existsFiles = new File[] {
                 new File(sFilesDir.getPath() + "/snapshots/1.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/1.png"),
+                new File(sFilesDir.getPath() + "/snapshots/1.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/1_reduced.jpg"),
                 new File(sFilesDir.getPath() + "/snapshots/2.proto"),
-                new File(sFilesDir.getPath() + "/snapshots/2.png") };
+                new File(sFilesDir.getPath() + "/snapshots/2.jpg"),
+                new File(sFilesDir.getPath() + "/snapshots/2_reduced.jpg")};
         assertTrueForFiles(existsFiles, File::exists, " must exist");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 6fc6edb..5e7389d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -106,6 +106,7 @@
         Canvas c = buffer.lockCanvas();
         c.drawColor(Color.RED);
         buffer.unlockCanvasAndPost(c);
-        return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS);
+        return new TaskSnapshot(buffer, ORIENTATION_PORTRAIT, TEST_INSETS,
+                false /* reducedResolution */, 1f /* scale */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 65efd9c..ce632ae 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -73,8 +73,8 @@
     private final static Session sMockSession = mock(Session.class);
     // The default display is removed in {@link #setUp} and then we iterate over all displays to
     // make sure we don't collide with any existing display. If we run into no other display, the
-    // added display should be treated as default.
-    private static int sNextDisplayId = Display.DEFAULT_DISPLAY;
+    // added display should be treated as default. This cannot be the default display
+    private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1;
     static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
     private static int sNextTaskId = 0;
 
@@ -105,17 +105,23 @@
         sWm = TestWindowManagerPolicy.getWindowManagerService(context);
         sPolicy = (TestWindowManagerPolicy) sWm.mPolicy;
         sLayersController = new WindowLayersController(sWm);
-        sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId());
-        if (sDisplayContent != null) {
-            sDisplayContent.removeImmediately();
-        }
+
         // Make sure that display ids don't overlap, so there won't be several displays with same
         // ids among RootWindowContainer children.
         for (DisplayContent dc : sWm.mRoot.mChildren) {
             if (dc.getDisplayId() >= sNextDisplayId) {
                 sNextDisplayId = dc.getDisplayId() + 1;
             }
+
+            // The default display must be preserved as some tests require it to function
+            // (such as policy rotation).
+            if (dc.getDisplayId() != Display.DEFAULT_DISPLAY) {
+                // It is safe to remove these displays as new displays will always be created with
+                // new ids.
+                dc.removeImmediately();
+            }
         }
+
         context.getDisplay().getDisplayInfo(sDisplayInfo);
         sDisplayContent = createNewDisplay();
         sWm.mDisplayEnabled = true;
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index b12ed94..2757296 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.VibrationEffect;
 import android.test.suitebuilder.annotation.SmallTest;
 
 /**
@@ -48,7 +49,9 @@
      */
     public void testVibrate() throws RemoteException {
         try {
-            mVibratorService.vibrate(Process.myUid(), null, 2000, AudioManager.STREAM_ALARM,
+            final VibrationEffect effect =
+                    VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
+            mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
                     new Binder());
             fail("vibrate did not throw SecurityException as expected");
         } catch (SecurityException e) {
@@ -57,23 +60,6 @@
     }
 
     /**
-     * Test that calling {@link android.os.IVibratorService#vibratePattern(long[],
-     * int, android.os.IBinder)} requires permissions.
-     * <p>Tests permission:
-     *   {@link android.Manifest.permission#VIBRATE}
-     * @throws RemoteException
-     */
-    public void testVibratePattern() throws RemoteException {
-        try {
-            mVibratorService.vibratePattern(Process.myUid(), null, new long[] {0}, 0,
-                    AudioManager.STREAM_ALARM, new Binder());
-            fail("vibratePattern did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
      * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions.
      * <p>Tests permission:
      *   {@link android.Manifest.permission#VIBRATE}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 11328dc..e118889 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -174,6 +174,12 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static synchronized int[] nativeGetSupportedAxes(long native_instance) {
+        // nativeCreateFromTypefaceWithVariation is not supported so we do not keep the axes
+        return null;
+    }
+
+    @LayoutlibDelegate
     /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) {
         Typeface_Delegate delegate = sManager.getDelegate(native_instance);
         if (delegate == null) {