Merge "TIF: Add Active Format Description to TvTrackInfo" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 5934594..6107169 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29064,6 +29064,7 @@
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(java.lang.String);
     method public static final boolean is64Bit();
+    method public static boolean isApplicationUid(int);
     method public static final void killProcess(int);
     method public static final int myPid();
     method public static final int myTid();
@@ -29244,6 +29245,7 @@
   public final class UserHandle implements android.os.Parcelable {
     ctor public UserHandle(android.os.Parcel);
     method public int describeContents();
+    method public static android.os.UserHandle getUserHandleForUid(int);
     method public static android.os.UserHandle readFromParcel(android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
     method public static void writeToParcel(android.os.UserHandle, android.os.Parcel);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3dc4978..b2f9675 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -31357,6 +31357,7 @@
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(java.lang.String);
     method public static final boolean is64Bit();
+    method public static boolean isApplicationUid(int);
     method public static final void killProcess(int);
     method public static final int myPid();
     method public static final int myTid();
@@ -31583,6 +31584,7 @@
     ctor public UserHandle(android.os.Parcel);
     method public int describeContents();
     method public int getIdentifier();
+    method public static android.os.UserHandle getUserHandleForUid(int);
     method public deprecated boolean isOwner();
     method public boolean isSystem();
     method public static int myUserId();
diff --git a/api/test-current.txt b/api/test-current.txt
index ed90474..1f64b5e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -29073,6 +29073,7 @@
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(java.lang.String);
     method public static final boolean is64Bit();
+    method public static boolean isApplicationUid(int);
     method public static final void killProcess(int);
     method public static final int myPid();
     method public static final int myTid();
@@ -29254,6 +29255,7 @@
     ctor public UserHandle(android.os.Parcel);
     method public int describeContents();
     method public static int getAppId(int);
+    method public static android.os.UserHandle getUserHandleForUid(int);
     method public static android.os.UserHandle readFromParcel(android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
     method public static void writeToParcel(android.os.UserHandle, android.os.Parcel);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 980329f..3385a17 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -807,6 +807,8 @@
     }
 
     /**
+     * AnimatorSet is only reversible when the set contains no sequential animation, and no child
+     * animators have a start delay.
      * @hide
      */
     @Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 83f9357..5f3adda 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5730,4 +5730,32 @@
             return false;
         }
     }
+
+    /**
+     * @hide
+     * Returns whether the uninstall for {@code packageName} for the current user is in queue
+     * to be started
+     * @param packageName the package to check for
+     * @return whether the uninstall intent for {@code packageName} is pending
+     */
+    public boolean isUninstallInQueue(String packageName) {
+        try {
+            return mService.isUninstallInQueue(packageName);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     * @param packageName the package containing active DAs to be uninstalled
+     */
+    public void uninstallPackageWithActiveAdmins(String packageName) {
+        try {
+            mService.uninstallPackageWithActiveAdmins(packageName);
+        } catch (RemoteException re) {
+            Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, re);
+        }
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c6a5344..2758f41 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -293,4 +293,7 @@
     boolean getDeviceLoggingEnabled(in ComponentName admin);
     ParceledListSlice retrieveDeviceLogs(in ComponentName admin);
     ParceledListSlice retrievePreviousDeviceLogs(in ComponentName admin);
+
+    boolean isUninstallInQueue(String packageName);
+    void uninstallPackageWithActiveAdmins(String packageName);
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index c79dae5..9082482 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1038,10 +1038,10 @@
         }
 
         deviceEncryptedDataDir = Environment
-                .getDataUserDeviceEncryptedPackageDirectory(volumeUuid, userId, packageName)
+                .getDataUserDePackageDirectory(volumeUuid, userId, packageName)
                 .getAbsolutePath();
         credentialEncryptedDataDir = Environment
-                .getDataUserCredentialEncryptedPackageDirectory(volumeUuid, userId, packageName)
+                .getDataUserCePackageDirectory(volumeUuid, userId, packageName)
                 .getAbsolutePath();
 
         if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 766868d..8724a96 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -847,6 +847,9 @@
          * to make forward progress from the partial results and avoid waiting for the completed
          * result.</p>
          *
+         * <p>For a particular request, {@link #onCaptureProgressed} may happen before or after
+         * {@link #onCaptureStarted}.</p>
+         *
          * <p>Each request will generate at least {@code 1} partial results, and at most
          * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p>
          *
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e841dfe..59bf293 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -176,35 +176,37 @@
         return DIR_VENDOR_ROOT;
     }
 
-    /** {@hide} */
-    @Deprecated
-    public static File getSystemSecureDirectory() {
-        return getDataSystemDirectory();
-    }
-
-    /** {@hide} */
-    @Deprecated
-    public static File getSecureDataDirectory() {
-        return getDataDirectory();
-    }
-
     /**
-     * Return the system directory for a user. This is for use by system services to store
-     * files relating to the user. This directory will be automatically deleted when the user
-     * is removed.
+     * Return the system directory for a user. This is for use by system
+     * services to store files relating to the user. This directory will be
+     * automatically deleted when the user is removed.
      *
+     * @deprecated This directory is valid and still exists, but callers should
+     *             <em>strongly</em> consider switching to
+     *             {@link #getDataSystemCeDirectory(int)} which is protected
+     *             with user credentials or
+     *             {@link #getDataSystemDeDirectory(int)} which supports fast
+     *             user wipe.
      * @hide
      */
+    @Deprecated
     public static File getUserSystemDirectory(int userId) {
         return new File(new File(getDataSystemDirectory(), "users"), Integer.toString(userId));
     }
 
     /**
-     * Returns the config directory for a user. This is for use by system services to store files
-     * relating to the user which should be readable by any app running as that user.
+     * Returns the config directory for a user. This is for use by system
+     * services to store files relating to the user which should be readable by
+     * any app running as that user.
      *
+     * @deprecated This directory is valid and still exists, but callers should
+     *             <em>strongly</em> consider switching to
+     *             {@link #getDataMiscCeDirectory(int)} which is protected with
+     *             user credentials or {@link #getDataMiscDeDirectory(int)}
+     *             which supports fast user wipe.
      * @hide
      */
+    @Deprecated
     public static File getUserConfigDirectory(int userId) {
         return new File(new File(new File(
                 getDataDirectory(), "misc"), "user"), Integer.toString(userId));
@@ -232,13 +234,28 @@
     }
 
     /** {@hide} */
-    public static File getDataSystemCredentialEncryptedDirectory() {
-        return new File(getDataDirectory(), "system_ce");
+    public static File getDataSystemCeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId));
     }
 
     /** {@hide} */
-    public static File getDataSystemCredentialEncryptedDirectory(int userId) {
-        return new File(getDataSystemCredentialEncryptedDirectory(), String.valueOf(userId));
+    public static File getDataSystemDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "system_de", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataMiscDirectory() {
+        return new File(getDataDirectory(), "misc");
+    }
+
+    /** {@hide} */
+    public static File getDataMiscCeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "misc_ce", String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getDataMiscDeDirectory(int userId) {
+        return buildPath(getDataDirectory(), "misc_de", String.valueOf(userId));
     }
 
     /** {@hide} */
@@ -252,57 +269,37 @@
     }
 
     /** {@hide} */
-    @Deprecated
-    public static File getDataUserDirectory(String volumeUuid) {
-        return getDataUserCredentialEncryptedDirectory(volumeUuid);
-    }
-
-    /** {@hide} */
-    @Deprecated
-    public static File getDataUserDirectory(String volumeUuid, int userId) {
-        return getDataUserCredentialEncryptedDirectory(volumeUuid, userId);
-    }
-
-    /** {@hide} */
-    @Deprecated
-    public static File getDataUserPackageDirectory(String volumeUuid, int userId,
-            String packageName) {
-        return getDataUserCredentialEncryptedPackageDirectory(volumeUuid, userId, packageName);
-    }
-
-    /** {@hide} */
-    public static File getDataUserCredentialEncryptedDirectory(String volumeUuid) {
+    public static File getDataUserCeDirectory(String volumeUuid) {
         return new File(getDataDirectory(volumeUuid), "user");
     }
 
     /** {@hide} */
-    public static File getDataUserCredentialEncryptedDirectory(String volumeUuid, int userId) {
-        return new File(getDataUserCredentialEncryptedDirectory(volumeUuid),
-                String.valueOf(userId));
+    public static File getDataUserCeDirectory(String volumeUuid, int userId) {
+        return new File(getDataUserCeDirectory(volumeUuid), String.valueOf(userId));
     }
 
     /** {@hide} */
-    public static File getDataUserCredentialEncryptedPackageDirectory(String volumeUuid, int userId,
+    public static File getDataUserCePackageDirectory(String volumeUuid, int userId,
             String packageName) {
         // TODO: keep consistent with installd
-        return new File(getDataUserCredentialEncryptedDirectory(volumeUuid, userId), packageName);
+        return new File(getDataUserCeDirectory(volumeUuid, userId), packageName);
     }
 
     /** {@hide} */
-    public static File getDataUserDeviceEncryptedDirectory(String volumeUuid) {
+    public static File getDataUserDeDirectory(String volumeUuid) {
         return new File(getDataDirectory(volumeUuid), "user_de");
     }
 
     /** {@hide} */
-    public static File getDataUserDeviceEncryptedDirectory(String volumeUuid, int userId) {
-        return new File(getDataUserDeviceEncryptedDirectory(volumeUuid), String.valueOf(userId));
+    public static File getDataUserDeDirectory(String volumeUuid, int userId) {
+        return new File(getDataUserDeDirectory(volumeUuid), String.valueOf(userId));
     }
 
     /** {@hide} */
-    public static File getDataUserDeviceEncryptedPackageDirectory(String volumeUuid, int userId,
+    public static File getDataUserDePackageDirectory(String volumeUuid, int userId,
             String packageName) {
         // TODO: keep consistent with installd
-        return new File(getDataUserDeviceEncryptedDirectory(volumeUuid, userId), packageName);
+        return new File(getDataUserDeDirectory(volumeUuid, userId), packageName);
     }
 
     /**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index b51d2dfb..9984755 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -823,6 +823,16 @@
     }
 
     /**
+     * Returns whether the given uid belongs to an application.
+     * @param uid A kernel uid.
+     * @return Whether the uid corresponds to an application sandbox running in
+     *     a specific user.
+     */
+    public static boolean isApplicationUid(int uid) {
+        return UserHandle.isApp(uid);
+    }
+
+    /**
      * Returns whether the current process is in an isolated sandbox.
      * @hide
      */
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 24666fe..b3f4453 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -130,6 +130,15 @@
     }
 
     /**
+     * Returns the user for a given uid.
+     * @param uid A uid for an application running in a particular user.
+     * @return A {@link UserHandle} for that user.
+     */
+    public static UserHandle getUserHandleForUid(int uid) {
+        return of(getUserId(uid));
+    }
+
+    /**
      * Returns the user id for a given uid.
      * @hide
      */
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b0fb93b..3402989 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -45,6 +45,7 @@
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.provider.DocumentsContract;
 import android.service.chooser.ChooserTarget;
 import android.service.chooser.ChooserTargetService;
@@ -232,7 +233,7 @@
         // the case where we don't have access to credential encrypted storage we just won't
         // have our pinned target info.
         final File prefsFile = new File(new File(
-                Environment.getDataUserCredentialEncryptedPackageDirectory(null,
+                Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
                         context.getUserId(), context.getPackageName()),
                 "shared_prefs"),
                 PINNED_SHARED_PREFS_NAME + ".xml");
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 210adce..465c4d8 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -104,6 +104,9 @@
         }
 
         private boolean isSuggestionOfType(int suggestionMask) {
+            if (!mIsTranslated) { // Never suggest an untranslated locale
+                return false;
+            }
             return (mSuggestionFlags & suggestionMask) == suggestionMask;
         }
 
@@ -207,6 +210,27 @@
         }
     }
 
+    /*
+     * Show all the languages supported for a country in the suggested list.
+     * This is also handy for devices without SIM (tablets).
+     */
+    private static void addSuggestedLocalesForRegion(Locale locale) {
+        if (locale == null) {
+            return;
+        }
+        final String country = locale.getCountry();
+        if (country.isEmpty()) {
+            return;
+        }
+
+        for (LocaleInfo li : sLocaleCache.values()) {
+            if (country.equals(li.getLocale().getCountry())) {
+                // We don't need to differentiate between manual and SIM suggestions
+                li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
+            }
+        }
+    }
+
     public static void fillCache(Context context) {
         if (sFullyInitialized) {
             return;
@@ -256,6 +280,8 @@
             li.setTranslated(localizedLocales.contains(li.getLangScriptKey()));
         }
 
+        addSuggestedLocalesForRegion(Locale.getDefault());
+
         sFullyInitialized = true;
     }
 
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
index 7a3c598..14badb7 100644
--- a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -43,12 +43,13 @@
     return env;
 }
 
-static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener, jint id) {
     class AnimationListenerBridge : public AnimationListener {
     public:
-        AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
+        AnimationListenerBridge(JNIEnv* env, jobject finishListener, jint id) {
             mFinishListener = env->NewGlobalRef(finishListener);
             env->GetJavaVM(&mJvm);
+            mId = id;
         }
 
         virtual ~AnimationListenerBridge() {
@@ -63,7 +64,7 @@
             env->CallStaticVoidMethod(
                     gVectorDrawableAnimatorClassInfo.clazz,
                     gVectorDrawableAnimatorClassInfo.callOnFinished,
-                    mFinishListener);
+                    mFinishListener, mId);
             releaseJavaObject();
         }
 
@@ -76,8 +77,9 @@
 
         JavaVM* mJvm;
         jobject mFinishListener;
+        jint mId;
     };
-    return new AnimationListenerBridge(env, finishListener);
+    return new AnimationListenerBridge(env, finishListener, id);
 }
 
 static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
@@ -142,15 +144,16 @@
     holder->setPropertyDataSource(propertyData, length);
     env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
 }
-static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
     PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
-    // TODO: keep a ref count in finish listener
-    AnimationListener* listener = createAnimationListener(env, finishListener);
+    AnimationListener* listener = createAnimationListener(env, finishListener, id);
     set->start(listener);
 }
 
-static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
-    // TODO: implement reverse
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener, jint id) {
+    PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+    AnimationListener* listener = createAnimationListener(env, finishListener, id);
+    set->reverse(listener);
 }
 
 static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
@@ -172,8 +175,8 @@
     {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
     {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
     {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
-    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
-    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
+    {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)start},
+    {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V", (void*)reverse},
     {"nEnd", "!(J)V", (void*)end},
     {"nReset", "!(J)V", (void*)reset},
 };
@@ -186,7 +189,7 @@
 
     gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
             env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
-            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
+            "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;I)V");
     return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
             gMethods, NELEM(gMethods));
 }
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index 0926e9b..c9eac79 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -184,7 +184,7 @@
 
 static void end(JNIEnv* env, jobject clazz, jlong animatorPtr) {
     BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
-    animator->end();
+    animator->cancel();
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index dd0e456..ac77007 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -322,7 +322,11 @@
 void NotifyHandler::handleMessage(const Message& message) {
     JNIEnv* env = getenv(mVm);
 
-    jobject target = env->NewLocalRef(mObserver->getObserverReference());
+    ObserverProxy* observer = mObserver.get();
+    LOG_ALWAYS_FATAL_IF(observer == nullptr, "received message with no observer configured");
+    LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "received message with no data to report");
+
+    jobject target = env->NewLocalRef(observer->getObserverReference());
 
     if (target != nullptr) {
         jlongArray javaBuffer = get_metrics_buffer(env, target);
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index 63df5be..61753b1 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -25,6 +25,7 @@
     <style name="Theme.DeviceDefault.Light.Dialog" parent="Theme.Micro.Dialog" />
     <style name="Theme.DeviceDefault.Light.DialogWhenLarge" parent="Theme.Micro.Dialog" />
     <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Micro.Dialog.Alert" />
+    <style name="Theme.DeviceDefault.Settings" parent="Theme.Micro" />
     <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Micro" />
 
 </resources>
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index af8ccf5..99bc306 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -238,9 +238,6 @@
             mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
         }
         mAnimatedVectorState.mVectorDrawable.draw(canvas);
-        if (isStarted()) {
-            invalidateSelf();
-        }
     }
 
     @Override
@@ -611,10 +608,6 @@
         return mAnimatorSet.isRunning();
     }
 
-    private boolean isStarted() {
-        return mAnimatorSet.isStarted();
-    }
-
     /**
      * Resets the AnimatedVectorDrawable to the start state as specified in the animators.
      */
@@ -626,12 +619,6 @@
     @Override
     public void start() {
         ensureAnimatorSet();
-
-        // If any one of the animator has not ended, do nothing.
-        if (isStarted()) {
-            return;
-        }
-
         mAnimatorSet.start();
         invalidateSelf();
     }
@@ -652,6 +639,7 @@
     @Override
     public void stop() {
         mAnimatorSet.end();
+        invalidateSelf();
     }
 
     /**
@@ -774,6 +762,9 @@
      * @hide
      */
     public static class VectorDrawableAnimator {
+        private static final int NONE = 0;
+        private static final int START_ANIMATION = 1;
+        private static final int REVERSE_ANIMATION = 2;
         private AnimatorListener mListener = null;
         private final LongArray mStartDelays = new LongArray();
         private PropertyValuesHolder.PropertyValues mTmpValues =
@@ -782,7 +773,6 @@
         private boolean mContainsSequentialAnimators = false;
         private boolean mStarted = false;
         private boolean mInitialized = false;
-        private boolean mAnimationPending = false;
         private boolean mIsReversible = false;
         // This needs to be set before parsing starts.
         private boolean mShouldIgnoreInvalidAnim;
@@ -790,7 +780,8 @@
         private final VirtualRefBasePtr mSetRefBasePtr;
         private WeakReference<RenderNode> mTarget = null;
         private WeakReference<RenderNode> mLastSeenTarget = null;
-
+        private int mLastListenerId = 0;
+        private int mPendingAnimationAction = NONE;
 
         VectorDrawableAnimator() {
             mSetPtr = nCreateAnimatorSet();
@@ -810,6 +801,7 @@
             mInitialized = true;
 
             // Check reversible.
+            mIsReversible = true;
             if (mContainsSequentialAnimators) {
                 mIsReversible = false;
             } else {
@@ -821,7 +813,6 @@
                     }
                 }
             }
-            mIsReversible = true;
         }
 
         private void parseAnimatorSet(AnimatorSet set, long startTime) {
@@ -1042,27 +1033,22 @@
          * to the last seen RenderNode target and start right away.
          */
         protected void recordLastSeenTarget(DisplayListCanvas canvas) {
-            if (mAnimationPending) {
-                mLastSeenTarget = new WeakReference<RenderNode>(
-                        RenderNodeAnimatorSetHelper.getTarget(canvas));
+            mLastSeenTarget = new WeakReference<RenderNode>(
+                    RenderNodeAnimatorSetHelper.getTarget(canvas));
+            if (mPendingAnimationAction != NONE) {
                 if (DBG_ANIMATION_VECTOR_DRAWABLE) {
                     Log.d(LOGTAG, "Target is set in the next frame");
                 }
-                mAnimationPending = false;
-                start();
-            } else {
-                mLastSeenTarget = new WeakReference<RenderNode>(
-                        RenderNodeAnimatorSetHelper.getTarget(canvas));
+                if (mPendingAnimationAction == START_ANIMATION) {
+                    start();
+                } else if (mPendingAnimationAction == REVERSE_ANIMATION) {
+                    reverse();
+                }
+                mPendingAnimationAction = NONE;
             }
-
         }
 
         private boolean setTarget(RenderNode node) {
-            if (mTarget != null && mTarget.get() != null) {
-                // TODO: Maybe we want to support target change.
-                throw new IllegalStateException("Target already set!");
-            }
-
             node.addAnimator(this);
             mTarget = new WeakReference<RenderNode>(node);
             return true;
@@ -1081,12 +1067,8 @@
                 return;
             }
 
-            if (mStarted) {
-                return;
-            }
-
             if (!useLastSeenTarget()) {
-                mAnimationPending = true;
+                mPendingAnimationAction = START_ANIMATION;
                 return;
             }
 
@@ -1094,38 +1076,45 @@
                 Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
             }
 
-           nStart(mSetPtr, this);
+            mStarted = true;
+            nStart(mSetPtr, this, ++mLastListenerId);
             if (mListener != null) {
                 mListener.onAnimationStart(null);
             }
-            mStarted = true;
         }
 
         public void end() {
-            if (mInitialized && mStarted) {
+            if (mInitialized && useLastSeenTarget()) {
+                // If no target has ever been set, no-op
                 nEnd(mSetPtr);
-                onAnimationEnd();
             }
         }
 
-        void reset() {
-            if (!mInitialized) {
-                return;
+        public void reset() {
+            if (mInitialized && useLastSeenTarget()) {
+                // If no target has ever been set, no-op
+                nReset(mSetPtr);
             }
-            // TODO: Need to implement reset.
-            Log.w(LOGTAG, "Reset is yet to be implemented");
-            nReset(mSetPtr);
         }
 
         // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
         // animators or when the animator set has a start delay
         void reverse() {
-            if (!mIsReversible) {
+            if (!mIsReversible || !mInitialized) {
                 return;
             }
-            // TODO: Need to support reverse (non-public API)
-            Log.w(LOGTAG, "Reverse is yet to be implemented");
-            nReverse(mSetPtr, this);
+            if (!useLastSeenTarget()) {
+                mPendingAnimationAction = REVERSE_ANIMATION;
+                return;
+            }
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "Target is set. Reversing VDAnimatorSet from java");
+            }
+            mStarted = true;
+            nReverse(mSetPtr, this, ++mLastListenerId);
+            if (mListener != null) {
+                mListener.onAnimationStart(null);
+            }
         }
 
         public long getAnimatorNativePtr() {
@@ -1155,7 +1144,13 @@
             mListener = null;
         }
 
-        private void onAnimationEnd() {
+        private void onAnimationEnd(int listenerId) {
+            if (listenerId != mLastListenerId) {
+                return;
+            }
+            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "on finished called from native");
+            }
             mStarted = false;
             if (mListener != null) {
                 mListener.onAnimationEnd(null);
@@ -1164,11 +1159,8 @@
         }
 
         // onFinished: should be called from native
-        private static void callOnFinished(VectorDrawableAnimator set) {
-            if (DBG_ANIMATION_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "on finished called from native");
-            }
-            set.onAnimationEnd();
+        private static void callOnFinished(VectorDrawableAnimator set, int id) {
+            set.onAnimationEnd(id);
         }
     }
 
@@ -1188,8 +1180,8 @@
     private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
             float endValue);
     private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
-    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
-    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
+    private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set, int id);
+    private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set, int id);
     private static native void nEnd(long animatorSetPtr);
     private static native void nReset(long animatorSetPtr);
 }
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 7bd2b24..372bcb3 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -42,7 +42,8 @@
         , mStartTime(0)
         , mDuration(300)
         , mStartDelay(0)
-        , mMayRunAsync(true) {
+        , mMayRunAsync(true)
+        , mPlayTime(0) {
 }
 
 BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
@@ -85,20 +86,113 @@
     onAttached();
 }
 
+void BaseRenderNodeAnimator::start() {
+    mStagingPlayState = PlayState::Running;
+    mStagingRequests.push_back(Request::Start);
+    onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::cancel() {
+    mStagingPlayState = PlayState::Finished;
+    mStagingRequests.push_back(Request::Cancel);
+    onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::reset() {
+    mStagingPlayState = PlayState::Finished;
+    mStagingRequests.push_back(Request::Reset);
+    onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::reverse() {
+    mStagingPlayState = PlayState::Reversing;
+    mStagingRequests.push_back(Request::Reverse);
+    onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::end() {
+    mStagingPlayState = PlayState::Finished;
+    mStagingRequests.push_back(Request::End);
+    onStagingPlayStateChanged();
+}
+
+void BaseRenderNodeAnimator::resolveStagingRequest(Request request) {
+    switch (request) {
+    case Request::Start:
+        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
+                        mPlayTime : 0;
+        mPlayState = PlayState::Running;
+        break;
+    case Request::Reverse:
+        mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ?
+                        mPlayTime : mDuration;
+        mPlayState = PlayState::Reversing;
+        break;
+    case Request::Reset:
+        mPlayTime = 0;
+        mPlayState = PlayState::Finished;
+        break;
+    case Request::Cancel:
+        mPlayState = PlayState::Finished;
+        break;
+    case Request::End:
+        mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
+        mPlayState = PlayState::Finished;
+        break;
+    default:
+        LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request));
+    };
+}
+
 void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
     if (!mHasStartValue) {
         doSetStartValue(getValue(mTarget));
     }
-    if (mStagingPlayState > mPlayState) {
-        if (mStagingPlayState == PlayState::Restarted) {
-            mStagingPlayState = PlayState::Running;
+
+    if (!mStagingRequests.empty()) {
+        // Keep track of the play state and play time before they are changed when
+        // staging requests are resolved.
+        nsecs_t currentPlayTime = mPlayTime;
+        PlayState prevFramePlayState = mPlayState;
+
+        // Resolve staging requests one by one.
+        for (Request request : mStagingRequests) {
+            resolveStagingRequest(request);
         }
-        mPlayState = mStagingPlayState;
-        // Oh boy, we're starting! Man the battle stations!
-        if (mPlayState == PlayState::Running) {
-            transitionToRunning(context);
-        } else if (mPlayState == PlayState::Finished) {
+        mStagingRequests.clear();
+
+        if (mStagingPlayState == PlayState::Finished) {
+            // Set the staging play time and end the animation
+            updatePlayTime(mPlayTime);
             callOnFinishedListener(context);
+        } else if (mStagingPlayState == PlayState::Running
+                || mStagingPlayState == PlayState::Reversing) {
+            bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState;
+            if (prevFramePlayState != mStagingPlayState) {
+                transitionToRunning(context);
+            }
+            if (changed) {
+                // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was
+                // requested from UI thread). It is achieved by modifying mStartTime, such that
+                // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the
+                // case of reversing)
+                nsecs_t currentFrameTime = context.frameTimeMs();
+                if (mPlayState == PlayState::Reversing) {
+                    // Reverse is not supported for animations with a start delay, so here we
+                    // assume no start delay.
+                    mStartTime = currentFrameTime  - (mDuration - mPlayTime);
+                } else {
+                    // Animation should play forward
+                    if (mPlayTime == 0) {
+                        // If the request is to start from the beginning, include start delay.
+                        mStartTime = currentFrameTime + mStartDelay;
+                    } else {
+                        // If the request is to seek to a non-zero play time, then we skip start
+                        // delay.
+                        mStartTime = currentFrameTime - mPlayTime;
+                    }
+                }
+            }
         }
     }
 }
@@ -136,37 +230,37 @@
 
     // This should be set before setValue() so animators can query this time when setValue
     // is called.
-    nsecs_t currentFrameTime = context.frameTimeMs();
-    onPlayTimeChanged(currentFrameTime - mStartTime);
+    nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime;
+    bool finished = updatePlayTime(currentPlayTime);
+    if (finished && mPlayState != PlayState::Finished) {
+        mPlayState = PlayState::Finished;
+        callOnFinishedListener(context);
+    }
+    return finished;
+}
 
+bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) {
+    mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime;
+    onPlayTimeChanged(mPlayTime);
     // If BaseRenderNodeAnimator is handling the delay (not typical), then
     // because the staging properties reflect the final value, we always need
     // to call setValue even if the animation isn't yet running or is still
     // being delayed as we need to override the staging value
-    if (mStartTime > context.frameTimeMs()) {
+    if (playTime < 0) {
         setValue(mTarget, mFromValue);
         return false;
     }
 
     float fraction = 1.0f;
-
-    if (mPlayState == PlayState::Running && mDuration > 0) {
-        fraction = (float)(currentFrameTime - mStartTime) / mDuration;
+    if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) {
+        fraction = mPlayTime / (float) mDuration;
     }
-    if (fraction >= 1.0f) {
-        fraction = 1.0f;
-        mPlayState = PlayState::Finished;
-    }
+    fraction = MathUtils::clamp(fraction, 0.0f, 1.0f);
 
     fraction = mInterpolator->interpolate(fraction);
     setValue(mTarget, mFromValue + (mDeltaValue * fraction));
 
-    if (mPlayState == PlayState::Finished) {
-        callOnFinishedListener(context);
-        return true;
-    }
-
-    return false;
+    return playTime >= mDuration;
 }
 
 void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) {
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 2c9c9c3..fcbc11b 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -24,6 +24,8 @@
 
 #include "utils/Macros.h"
 
+#include <vector>
+
 namespace android {
 namespace uirenderer {
 
@@ -59,14 +61,14 @@
         mMayRunAsync = mayRunAsync;
     }
     bool mayRunAsync() { return mMayRunAsync; }
-    ANDROID_API void start() {
-        if (mStagingPlayState == PlayState::NotStarted) {
-            mStagingPlayState = PlayState::Running;
-        } else {
-            mStagingPlayState = PlayState::Restarted;
-        }
-        onStagingPlayStateChanged(); }
-    ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
+    ANDROID_API void start();
+    ANDROID_API void reset();
+    ANDROID_API void reverse();
+    // Terminates the animation at its current progress.
+    ANDROID_API void cancel();
+
+    // Terminates the animation and skip to the end of the animation.
+    ANDROID_API void end();
 
     void attach(RenderNode* target);
     virtual void onAttached() {}
@@ -74,36 +76,41 @@
     void pushStaging(AnimationContext& context);
     bool animate(AnimationContext& context);
 
-    bool isRunning() { return mPlayState == PlayState::Running; }
+    bool isRunning() { return mPlayState == PlayState::Running
+            || mPlayState == PlayState::Reversing; }
     bool isFinished() { return mPlayState == PlayState::Finished; }
     float finalValue() { return mFinalValue; }
 
     ANDROID_API virtual uint32_t dirtyMask() = 0;
 
     void forceEndNow(AnimationContext& context);
+    RenderNode* target() { return mTarget; }
 
 protected:
     // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
     // thread and Render Thread animation state, respectively.
     // From the UI thread, mStagingPlayState transition looks like
-    // NotStarted -> Running -> Finished
-    //                ^            |
-    //                |            |
-    //            Restarted <------
+    // NotStarted -> Running/Reversing -> Finished
+    //                ^                     |
+    //                |                     |
+    //                ----------------------
     // Note: For mStagingState, the Finished state (optional) is only set when the animation is
     // terminated by user.
     //
     // On Render Thread, mPlayState transition:
-    // NotStart -> Running -> Finished
-    //                ^            |
-    //                |            |
-    //                -------------
+    // NotStart -> Running/Reversing-> Finished
+    //                ^                 |
+    //                |                 |
+    //                ------------------
+    // Note that if the animation is in Running/Reversing state, calling start or reverse again
+    // would do nothing if the animation has the same play direction as the request; otherwise,
+    // the animation would start from where it is and change direction (i.e. Reversing <-> Running)
 
     enum class PlayState {
         NotStarted,
         Running,
+        Reversing,
         Finished,
-        Restarted,
     };
 
     BaseRenderNodeAnimator(float finalValue);
@@ -111,7 +118,6 @@
 
     virtual float getValue(RenderNode* target) const = 0;
     virtual void setValue(RenderNode* target, float value) = 0;
-    RenderNode* target() { return mTarget; }
 
     void callOnFinishedListener(AnimationContext& context);
 
@@ -132,13 +138,28 @@
     nsecs_t mDuration;
     nsecs_t mStartDelay;
     bool mMayRunAsync;
+    // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being
+    // the beginning of the animation, will reach mDuration at the end of an animation.
+    nsecs_t mPlayTime;
 
     sp<AnimationListener> mListener;
 
 private:
+    enum class Request {
+        Start,
+        Reverse,
+        Reset,
+        Cancel,
+        End
+    };
     inline void checkMutable();
     virtual void transitionToRunning(AnimationContext& context);
     void doSetStartValue(float value);
+    bool updatePlayTime(nsecs_t playTime);
+    void resolveStagingRequest(Request request);
+
+    std::vector<Request> mStagingRequests;
+
 };
 
 class RenderPropertyAnimator : public BaseRenderNodeAnimator {
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index cd30b18..2b49b47 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -27,9 +27,8 @@
 
 using namespace std;
 
-static void unref(BaseRenderNodeAnimator* animator) {
+static void detach(sp<BaseRenderNodeAnimator>& animator) {
     animator->detach();
-    animator->decStrong(nullptr);
 }
 
 AnimatorManager::AnimatorManager(RenderNode& parent)
@@ -38,14 +37,12 @@
 }
 
 AnimatorManager::~AnimatorManager() {
-    for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
-    for_each(mAnimators.begin(), mAnimators.end(), unref);
+    for_each(mNewAnimators.begin(), mNewAnimators.end(), detach);
+    for_each(mAnimators.begin(), mAnimators.end(), detach);
 }
 
 void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
-    animator->incStrong(nullptr);
-    animator->attach(&mParent);
-    mNewAnimators.push_back(animator.get());
+    mNewAnimators.emplace_back(animator.get());
 }
 
 void AnimatorManager::setAnimationHandle(AnimationHandle* handle) {
@@ -56,25 +53,31 @@
             &mParent, mParent.getName());
 }
 
-template<typename T>
-static void move_all(T& source, T& dest) {
-    dest.reserve(source.size() + dest.size());
-    for (typename T::iterator it = source.begin(); it != source.end(); it++) {
-        dest.push_back(*it);
-    }
-    source.clear();
-}
-
 void AnimatorManager::pushStaging() {
     if (mNewAnimators.size()) {
         LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
                 "Trying to start new animators on %p (%s) without an animation handle!",
                 &mParent, mParent.getName());
-        // Since this is a straight move, we don't need to inc/dec the ref count
-        move_all(mNewAnimators, mAnimators);
+        // Only add animators that are not already in the on-going animator list.
+        for (auto& animator : mNewAnimators) {
+            RenderNode* targetRenderNode = animator->target();
+            if (targetRenderNode == &mParent) {
+                // Animator already in the animator list: skip adding again
+                continue;
+            }
+
+            if (targetRenderNode){
+                // If the animator is already in another RenderNode's animator list, remove animator from
+                // that list and add animator to current RenderNode's list.
+                targetRenderNode->animators().removeActiveAnimator(animator);
+            }
+            animator->attach(&mParent);
+            mAnimators.push_back(std::move(animator));
+        }
+        mNewAnimators.clear();
     }
-    for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
-        (*it)->pushStaging(mAnimationHandle->context());
+    for (auto& animator : mAnimators) {
+        animator->pushStaging(mAnimationHandle->context());
     }
 }
 
@@ -83,11 +86,11 @@
     AnimateFunctor(TreeInfo& info, AnimationContext& context)
             : dirtyMask(0), mInfo(info), mContext(context) {}
 
-    bool operator() (BaseRenderNodeAnimator* animator) {
+    bool operator() (sp<BaseRenderNodeAnimator>& animator) {
         dirtyMask |= animator->dirtyMask();
         bool remove = animator->animate(mContext);
         if (remove) {
-            animator->decStrong(nullptr);
+            animator->detach();
         } else {
             if (animator->isRunning()) {
                 mInfo.out.hasAnimations = true;
@@ -129,20 +132,18 @@
 
 uint32_t AnimatorManager::animateCommon(TreeInfo& info) {
     AnimateFunctor functor(info, mAnimationHandle->context());
-    std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
-    newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+    auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
     mAnimators.erase(newEnd, mAnimators.end());
     mAnimationHandle->notifyAnimationsRan();
     mParent.mProperties.updateMatrix();
     return functor.dirtyMask;
 }
 
-static void endStagingAnimator(BaseRenderNodeAnimator* animator) {
-    animator->end();
+static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) {
+    animator->cancel();
     if (animator->listener()) {
-        animator->listener()->onAnimationFinished(animator);
+        animator->listener()->onAnimationFinished(animator.get());
     }
-    animator->decStrong(nullptr);
 }
 
 void AnimatorManager::endAllStagingAnimators() {
@@ -153,13 +154,16 @@
     mNewAnimators.clear();
 }
 
+void AnimatorManager::removeActiveAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+    std::remove(mAnimators.begin(), mAnimators.end(), animator);
+}
+
 class EndActiveAnimatorsFunctor {
 public:
     EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {}
 
-    void operator() (BaseRenderNodeAnimator* animator) {
+    void operator() (sp<BaseRenderNodeAnimator>& animator) {
         animator->forceEndNow(mContext);
-        animator->decStrong(nullptr);
     }
 
 private:
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
index fb75eb8..c24ef47 100644
--- a/libs/hwui/AnimatorManager.h
+++ b/libs/hwui/AnimatorManager.h
@@ -62,13 +62,17 @@
 private:
     uint32_t animateCommon(TreeInfo& info);
 
+    // This would remove the animator from mAnimators list. It should only be called during
+    // push staging.
+    void removeActiveAnimator(const sp<BaseRenderNodeAnimator>& animator);
+
     RenderNode& mParent;
     AnimationHandle* mAnimationHandle;
 
     // To improve the efficiency of resizing & removing from the vector
     // use manual ref counting instead of sp<>.
-    std::vector<BaseRenderNodeAnimator*> mNewAnimators;
-    std::vector<BaseRenderNodeAnimator*> mAnimators;
+    std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators;
+    std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 3db28c9..5a5845a 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -100,7 +100,7 @@
     static BakedOpState* tryConstruct(LinearAllocator& allocator,
             Snapshot& snapshot, const RecordedOp& recordedOp) {
         if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-        BakedOpState* bakedState = new (allocator) BakedOpState(
+        BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
                 allocator, snapshot, recordedOp, false);
         if (bakedState->computedState.clippedBounds.isEmpty()) {
             // bounds are empty, so op is rejected
@@ -124,7 +124,7 @@
                 ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
                 : true;
 
-        BakedOpState* bakedState = new (allocator) BakedOpState(
+        BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
                 allocator, snapshot, recordedOp, expandForStroke);
         if (bakedState->computedState.clippedBounds.isEmpty()) {
             // bounds are empty, so op is rejected
@@ -140,16 +140,12 @@
         if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
 
         // clip isn't empty, so construct the op
-        return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
+        return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
     }
 
     static BakedOpState* directConstruct(LinearAllocator& allocator,
             const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
-        return new (allocator) BakedOpState(clip, dstRect, recordedOp);
-    }
-
-    static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
+        return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
     }
 
     // computed state:
@@ -162,6 +158,8 @@
     const RecordedOp* op;
 
 private:
+    friend class LinearAllocator;
+
     BakedOpState(LinearAllocator& allocator, Snapshot& snapshot,
             const RecordedOp& recordedOp, bool expandForStroke)
             : computedState(allocator, snapshot, recordedOp, expandForStroke)
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index c2e14a2..6d5833b 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -45,7 +45,7 @@
 };
 
 DamageAccumulator::DamageAccumulator() {
-    mHead = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+    mHead = mAllocator.create_trivial<DirtyStack>();
     memset(mHead, 0, sizeof(DirtyStack));
     // Create a root that we will not pop off
     mHead->prev = mHead;
@@ -78,7 +78,7 @@
 
 void DamageAccumulator::pushCommon() {
     if (!mHead->next) {
-        DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+        DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>();
         nextFrame->next = nullptr;
         nextFrame->prev = mHead;
         mHead->next = nextFrame;
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 2d5979f..98ccf11 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -49,11 +49,6 @@
 
 class DeferredDisplayState {
 public:
-    static void* operator new(size_t size) = delete;
-    static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
-    }
-
     // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped
     Rect mBounds;
 
@@ -124,7 +119,7 @@
     DeferredDisplayList(const DeferredDisplayList& other); // disallow copy
 
     DeferredDisplayState* createState() {
-        return new (mAllocator) DeferredDisplayState();
+        return mAllocator.create_trivial<DeferredDisplayState>();
     }
 
     void tryRecycleState(DeferredDisplayState* state) {
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index e5711e3..a703e22 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -251,7 +251,7 @@
     inline const T* refBuffer(const T* srcBuffer, int32_t count) {
         if (!srcBuffer) return nullptr;
 
-        T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T));
+        T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T));
         memcpy(dstBuffer, srcBuffer, count * sizeof(T));
         return dstBuffer;
     }
@@ -320,8 +320,7 @@
         // correctly, such as creating the bitmap from scratch, drawing with it, changing its
         // contents, and drawing again. The only fix would be to always copy it the first time,
         // which doesn't seem worth the extra cycles for this unlikely case.
-        SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
-        alloc().autoDestroy(localBitmap);
+        SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap);
         mDisplayList->bitmapResources.push_back(localBitmap);
         return localBitmap;
     }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 20501ba..98315d0 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -64,7 +64,9 @@
     static void operator delete(void* ptr) { LOG_ALWAYS_FATAL("delete not supported"); }
     static void* operator new(size_t size) = delete; /** PURPOSELY OMITTED **/
     static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
+        // FIXME: Quick hack to keep old pipeline working, delete this when
+        // we no longer need to support HWUI_NEWOPS := false
+        return allocator.alloc<char>(size);
     }
 
     enum OpLogFlag {
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 185acce..4f51036 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -209,7 +209,7 @@
         // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer)
         if (node.getLayer()) {
             // HW layer
-            LayerOp* drawLayerOp = new (mAllocator) LayerOp(node);
+            LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node);
             BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
             if (bakedOpState) {
                 // Node's layer already deferred, schedule it to render into parent layer
@@ -220,13 +220,13 @@
             // (temp layers are clipped to viewport, since they don't persist offscreen content)
             SkPaint saveLayerPaint;
             saveLayerPaint.setAlpha(properties.getAlpha());
-            deferBeginLayerOp(*new (mAllocator) BeginLayerOp(
+            deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>(
                     saveLayerBounds,
                     Matrix4::identity(),
                     nullptr, // no record-time clip - need only respect defer-time one
                     &saveLayerPaint));
             deferNodeOps(node);
-            deferEndLayerOp(*new (mAllocator) EndLayerOp());
+            deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>());
         } else {
             deferNodeOps(node);
         }
@@ -549,7 +549,7 @@
 void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
     const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
     SkPaint* paint = op.vectorDrawable->getPaint();
-    const BitmapRectOp* resolvedOp = new (mAllocator) BitmapRectOp(op.unmappedBounds,
+    const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds,
             op.localMatrix,
             op.localClip,
             paint,
@@ -565,7 +565,7 @@
     float y = *(op.y);
     float radius = *(op.radius);
     Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius);
-    const OvalOp* resolvedOp = new (mAllocator) OvalOp(
+    const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>(
             unmappedBounds,
             op.localMatrix,
             op.localClip,
@@ -626,7 +626,7 @@
 void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
     // allocate a temporary round rect op (with mAllocator, so it persists until render), so the
     // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
-    const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp(
+    const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>(
             Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
             op.localMatrix,
             op.localClip,
@@ -754,7 +754,7 @@
 
     // record the draw operation into the previous layer's list of draw commands
     // uses state from the associated beginLayerOp, since it has all the state needed for drawing
-    LayerOp* drawLayerOp = new (mAllocator) LayerOp(
+    LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(
             beginLayerOp.unmappedBounds,
             beginLayerOp.localMatrix,
             beginLayerOp.localClip,
@@ -788,7 +788,7 @@
     /**
      * First, defer an operation to copy out the content from the rendertarget into a layer.
      */
-    auto copyToOp = new (mAllocator) CopyToLayerOp(op, layerHandle);
+    auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle);
     BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator,
             &(currentLayer().viewportClip), dstRect, *copyToOp);
     currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer);
@@ -803,7 +803,7 @@
      * And stash an operation to copy that layer back under the rendertarget until
      * a balanced EndUnclippedLayerOp is seen
      */
-    auto copyFromOp = new (mAllocator) CopyFromLayerOp(op, layerHandle);
+    auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle);
     bakedState = BakedOpState::directConstruct(mAllocator,
             &(currentLayer().viewportClip), dstRect, *copyFromOp);
     currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index 7170d4f..1ba3bf2 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -64,10 +64,6 @@
 
 class OpBatch : public BatchBase {
 public:
-    static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
-    }
-
     OpBatch(batchid_t batchId, BakedOpState* op)
             : BatchBase(batchId, op, false) {
     }
@@ -80,10 +76,6 @@
 
 class MergingOpBatch : public BatchBase {
 public:
-    static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
-    }
-
     MergingOpBatch(batchid_t batchId, BakedOpState* op)
             : BatchBase(batchId, op, true)
             , mClipSideFlags(op->computedState.clipSideFlags) {
@@ -247,7 +239,7 @@
         // put the verts in the frame allocator, since
         //     1) SimpleRectsOps needs verts, not rects
         //     2) even if mClearRects stored verts, std::vectors will move their contents
-        Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex));
+        Vertex* const verts = (Vertex*) allocator.alloc<Vertex>(vertCount * sizeof(Vertex));
 
         Vertex* currentVert = verts;
         Rect bounds = mClearRects[0];
@@ -264,7 +256,7 @@
         // Flush all of these clears with a single draw
         SkPaint* paint = allocator.create<SkPaint>();
         paint->setXfermodeMode(SkXfermode::kClear_Mode);
-        SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds,
+        SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>(bounds,
                 Matrix4::identity(), nullptr, paint,
                 verts, vertCount);
         BakedOpState* bakedState = BakedOpState::directConstruct(allocator,
@@ -292,7 +284,7 @@
         targetBatch->batchOp(op);
     } else  {
         // new non-merging batch
-        targetBatch = new (allocator) OpBatch(batchId, op);
+        targetBatch = allocator.create<OpBatch>(batchId, op);
         mBatchLookup[batchId] = targetBatch;
         mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
     }
@@ -323,7 +315,7 @@
         targetBatch->mergeOp(op);
     } else  {
         // new merging batch
-        targetBatch = new (allocator) MergingOpBatch(batchId, op);
+        targetBatch = allocator.create<MergingOpBatch>(batchId, op);
         mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
 
         mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
index eca1afcc..b29f91f 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.cpp
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -17,6 +17,8 @@
 #include "PropertyValuesAnimatorSet.h"
 #include "RenderNode.h"
 
+#include <algorithm>
+
 namespace android {
 namespace uirenderer {
 
@@ -53,16 +55,26 @@
 }
 
 void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
-    for (size_t i = 0; i < mAnimators.size(); i++) {
-        mAnimators[i]->setCurrentPlayTime(playTime);
+    if (playTime == 0 && mDuration > 0) {
+        // Reset all the animators
+        for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) {
+            // Note that this set may containing animators modifying the same property, so when we
+            // reset the animators, we need to make sure the animators that end the first will
+            // have the final say on what the property value should be.
+            (*it)->setFraction(0);
+        }
+    } else if (playTime >= mDuration) {
+        // Skip all the animators to end
+        for (auto& anim : mAnimators) {
+            anim->setFraction(1);
+        }
+    } else {
+        for (auto& anim : mAnimators) {
+            anim->setCurrentPlayTime(playTime);
+        }
     }
 }
 
-void PropertyValuesAnimatorSet::reset() {
-    // TODO: implement reset through adding a play state because we need to support reset() even
-    // during an animation run.
-}
-
 void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
     init();
     mOneShotListener = listener;
@@ -70,20 +82,23 @@
 }
 
 void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
-// TODO: implement reverse
+    init();
+    mOneShotListener = listener;
+    BaseRenderNodeAnimator::reverse();
 }
 
 void PropertyValuesAnimatorSet::init() {
     if (mInitialized) {
         return;
     }
-    nsecs_t maxDuration = 0;
-    for (size_t i = 0; i < mAnimators.size(); i++) {
-        if (maxDuration < mAnimators[i]->getTotalDuration()) {
-            maxDuration = mAnimators[i]->getTotalDuration();
-        }
-    }
-    mDuration = maxDuration;
+
+    // Sort the animators by their total duration. Note that all the animators in the set start at
+    // the same time, so the ones with longer total duration (which includes start delay) will
+    // be the ones that end later.
+    std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) {
+        return a->getTotalDuration() < b->getTotalDuration();
+    });
+    mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration();
     mInitialized = true;
 }
 
@@ -106,18 +121,19 @@
 void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
     if (playTime >= mStartDelay && playTime < mTotalDuration) {
          nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
-         mLatestFraction = currentIterationPlayTime / (float) mDuration;
+         float fraction = currentIterationPlayTime / (float) mDuration;
+         setFraction(fraction);
     } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
-        mLatestFraction = 1.0f;
-    } else {
-        return;
+        // This makes sure we only set the fraction = 1 once. It is needed because there might
+        // be another animator modifying the same property after this animator finishes, we need
+        // to make sure we don't set conflicting values on the same property within one frame.
+        setFraction(1.0f);
     }
-
-    setFraction(mLatestFraction);
 }
 
 void PropertyAnimator::setFraction(float fraction) {
-    float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
+    mLatestFraction = fraction;
+    float interpolatedFraction = mInterpolator->interpolate(fraction);
     mPropertyValuesHolder->setFraction(interpolatedFraction);
 }
 
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
index 4c7ce52..c7ae7c0 100644
--- a/libs/hwui/PropertyValuesAnimatorSet.h
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -50,7 +50,6 @@
 
     void start(AnimationListener* listener);
     void reverse(AnimationListener* listener);
-    void reset();
 
     void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
             Interpolator* interpolators, int64_t startDelays,
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 16929b8..269e590 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -83,9 +83,9 @@
 
 void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
     if (removed.flags & Snapshot::kFlagIsFboLayer) {
-        addOp(new (alloc()) EndLayerOp());
+        addOp(alloc().create_trivial<EndLayerOp>());
     } else if (removed.flags & Snapshot::kFlagIsLayer) {
-        addOp(new (alloc()) EndUnclippedLayerOp());
+        addOp(alloc().create_trivial<EndUnclippedLayerOp>());
     }
 }
 
@@ -167,7 +167,7 @@
         snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
         snapshot.roundRectClipState = nullptr;
 
-        addOp(new (alloc()) BeginLayerOp(
+        addOp(alloc().create_trivial<BeginLayerOp>(
                 unmappedBounds,
                 *previous.transform, // transform to *draw* with
                 previousClip, // clip to *draw* with
@@ -175,7 +175,7 @@
     } else {
         snapshot.flags |= Snapshot::kFlagIsLayer;
 
-        addOp(new (alloc()) BeginUnclippedLayerOp(
+        addOp(alloc().create_trivial<BeginUnclippedLayerOp>(
                 unmappedBounds,
                 *mState.currentSnapshot()->transform,
                 getRecordedClip(),
@@ -241,7 +241,7 @@
 }
 
 void RecordingCanvas::drawPaint(const SkPaint& paint) {
-    addOp(new (alloc()) RectOp(
+    addOp(alloc().create_trivial<RectOp>(
             mState.getRenderTargetClipBounds(), // OK, since we've not passed transform
             Matrix4::identity(),
             getRecordedClip(),
@@ -261,7 +261,7 @@
     if (floatCount < 2) return;
     floatCount &= ~0x1; // round down to nearest two
 
-    addOp(new (alloc()) PointsOp(
+    addOp(alloc().create_trivial<PointsOp>(
             calcBoundsOfPoints(points, floatCount),
             *mState.currentSnapshot()->transform,
             getRecordedClip(),
@@ -272,7 +272,7 @@
     if (floatCount < 4) return;
     floatCount &= ~0x3; // round down to nearest four
 
-    addOp(new (alloc()) LinesOp(
+    addOp(alloc().create_trivial<LinesOp>(
             calcBoundsOfPoints(points, floatCount),
             *mState.currentSnapshot()->transform,
             getRecordedClip(),
@@ -280,7 +280,7 @@
 }
 
 void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
-    addOp(new (alloc()) RectOp(
+    addOp(alloc().create_trivial<RectOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -290,7 +290,7 @@
 void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) {
     if (rects == nullptr) return;
 
-    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc(vertexCount * sizeof(Vertex));
+    Vertex* rectData = (Vertex*) mDisplayList->allocator.alloc<Vertex>(vertexCount * sizeof(Vertex));
     Vertex* vertex = rectData;
 
     float left = FLT_MAX;
@@ -313,7 +313,7 @@
         right = std::max(right, r);
         bottom = std::max(bottom, b);
     }
-    addOp(new (alloc()) SimpleRectsOp(
+    addOp(alloc().create_trivial<SimpleRectsOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -347,7 +347,7 @@
 }
 void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
             float rx, float ry, const SkPaint& paint) {
-    addOp(new (alloc()) RoundRectOp(
+    addOp(alloc().create_trivial<RoundRectOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -367,7 +367,7 @@
     mDisplayList->ref(ry);
     mDisplayList->ref(paint);
     refBitmapsInShader(paint->value.getShader());
-    addOp(new (alloc()) RoundRectPropsOp(
+    addOp(alloc().create_trivial<RoundRectPropsOp>(
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
             &paint->value,
@@ -389,7 +389,7 @@
     mDisplayList->ref(radius);
     mDisplayList->ref(paint);
     refBitmapsInShader(paint->value.getShader());
-    addOp(new (alloc()) CirclePropsOp(
+    addOp(alloc().create_trivial<CirclePropsOp>(
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
             &paint->value,
@@ -397,7 +397,7 @@
 }
 
 void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
-    addOp(new (alloc()) OvalOp(
+    addOp(alloc().create_trivial<OvalOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -406,7 +406,7 @@
 
 void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
         float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
-    addOp(new (alloc()) ArcOp(
+    addOp(alloc().create_trivial<ArcOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -415,7 +415,7 @@
 }
 
 void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
-    addOp(new (alloc()) PathOp(
+    addOp(alloc().create_trivial<PathOp>(
             Rect(path.getBounds()),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -424,7 +424,7 @@
 
 void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
     mDisplayList->ref(tree);
-    addOp(new (alloc()) VectorDrawableOp(
+    addOp(alloc().create_trivial<VectorDrawableOp>(
             tree,
             Rect(tree->getBounds()),
             *(mState.currentSnapshot()->transform),
@@ -475,7 +475,7 @@
         drawBitmap(&bitmap, paint);
         restore();
     } else {
-        addOp(new (alloc()) BitmapRectOp(
+        addOp(alloc().create_trivial<BitmapRectOp>(
                 Rect(dstLeft, dstTop, dstRight, dstBottom),
                 *(mState.currentSnapshot()->transform),
                 getRecordedClip(),
@@ -487,7 +487,7 @@
 void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
             const float* vertices, const int* colors, const SkPaint* paint) {
     int vertexCount = (meshWidth + 1) * (meshHeight + 1);
-    addOp(new (alloc()) BitmapMeshOp(
+    addOp(alloc().create_trivial<BitmapMeshOp>(
             calcBoundsOfPoints(vertices, vertexCount * 2),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -499,7 +499,7 @@
 void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch,
             float dstLeft, float dstTop, float dstRight, float dstBottom,
             const SkPaint* paint) {
-    addOp(new (alloc()) PatchOp(
+    addOp(alloc().create_trivial<PatchOp>(
             Rect(dstLeft, dstTop, dstRight, dstBottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -515,7 +515,7 @@
     positions = refBuffer<float>(positions, glyphCount * 2);
 
     // TODO: either must account for text shadow in bounds, or record separate ops for text shadows
-    addOp(new (alloc()) TextOp(
+    addOp(alloc().create_trivial<TextOp>(
             Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -527,7 +527,7 @@
             float hOffset, float vOffset, const SkPaint& paint) {
     if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
     glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
-    addOp(new (alloc()) TextOnPathOp(
+    addOp(alloc().create_trivial<TextOnPathOp>(
             mState.getLocalClipBounds(), // TODO: explicitly define bounds
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -535,7 +535,7 @@
 }
 
 void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
-    addOp(new (alloc()) BitmapOp(
+    addOp(alloc().create_trivial<BitmapOp>(
             Rect(bitmap->width(), bitmap->height()),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -544,7 +544,7 @@
 
 void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
     auto&& stagingProps = renderNode->stagingProperties();
-    RenderNodeOp* op = new (alloc()) RenderNodeOp(
+    RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>(
             Rect(stagingProps.getWidth(), stagingProps.getHeight()),
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
@@ -570,7 +570,7 @@
     Matrix4 totalTransform(*(mState.currentSnapshot()->transform));
     totalTransform.multiply(layer->getTransform());
 
-    addOp(new (alloc()) TextureLayerOp(
+    addOp(alloc().create_trivial<TextureLayerOp>(
             Rect(layer->getWidth(), layer->getHeight()),
             totalTransform,
             getRecordedClip(),
@@ -579,7 +579,7 @@
 
 void RecordingCanvas::callDrawGLFunction(Functor* functor) {
     mDisplayList->functors.push_back(functor);
-    addOp(new (alloc()) FunctorOp(
+    addOp(alloc().create_trivial<FunctorOp>(
             mState.getLocalClipBounds(), // TODO: explicitly define bounds
             *(mState.currentSnapshot()->transform),
             getRecordedClip(),
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index cc14e61..719872d 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -219,7 +219,7 @@
     inline const T* refBuffer(const T* srcBuffer, int32_t count) {
         if (!srcBuffer) return nullptr;
 
-        T* dstBuffer = (T*) mDisplayList->allocator.alloc(count * sizeof(T));
+        T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T));
         memcpy(dstBuffer, srcBuffer, count * sizeof(T));
         return dstBuffer;
     }
@@ -290,8 +290,7 @@
         // correctly, such as creating the bitmap from scratch, drawing with it, changing its
         // contents, and drawing again. The only fix would be to always copy it the first time,
         // which doesn't seem worth the extra cycles for this unlikely case.
-        SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap);
-        alloc().autoDestroy(localBitmap);
+        SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap);
         mDisplayList->bitmapResources.push_back(localBitmap);
         return localBitmap;
     }
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index dbaa905..0ac2f14 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -46,7 +46,7 @@
 public:
     /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
     static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
+        return allocator.alloc<RoundRectClipState>(size);
     }
 
     bool areaRequiresRoundRectClip(const Rect& rect) const {
@@ -67,7 +67,7 @@
 public:
     /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
     static void* operator new(size_t size, LinearAllocator& allocator) {
-        return allocator.alloc(size);
+        return allocator.alloc<ProjectionPathMask>(size);
     }
 
     const SkPath* projectionMask;
diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp
index 5c44290..402a09c 100644
--- a/libs/hwui/tests/unit/LinearAllocatorTests.cpp
+++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp
@@ -30,7 +30,7 @@
 TEST(LinearAllocator, create) {
     LinearAllocator la;
     EXPECT_EQ(0u, la.usedSize());
-    la.alloc(64);
+    la.alloc<char>(64);
     // There's some internal tracking as well as padding
     // so the usedSize isn't strictly defined
     EXPECT_LE(64u, la.usedSize());
@@ -50,13 +50,12 @@
             la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
             la.create<SimplePair>();
         }
-        la.alloc(100);
+        la.alloc<char>(100);
         for (int i = 0; i < 5; i++) {
-            auto sd = new (la) TestUtils::SignalingDtor(destroyed + 5 + i);
-            la.autoDestroy(sd);
-            new (la) SimplePair();
+            la.create<TestUtils::SignalingDtor>(destroyed + 5 + i);
+            la.create_trivial<SimplePair>();
         }
-        la.alloc(100);
+        la.alloc<char>(100);
         for (int i = 0; i < 10; i++) {
             EXPECT_EQ(0, destroyed[i]);
         }
@@ -70,7 +69,7 @@
     int destroyed = 0;
     {
         LinearAllocator la;
-        auto addr = la.alloc(100);
+        auto addr = la.alloc<char>(100);
         EXPECT_LE(100u, la.usedSize());
         la.rewindIfLastAlloc(addr, 100);
         EXPECT_GT(16u, la.usedSize());
diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp
index e6a4c03..5bba420 100644
--- a/libs/hwui/utils/LinearAllocator.cpp
+++ b/libs/hwui/utils/LinearAllocator.cpp
@@ -81,10 +81,6 @@
 
 #define min(x,y) (((x) < (y)) ? (x) : (y))
 
-void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la) {
-    return la.alloc(size);
-}
-
 namespace android {
 namespace uirenderer {
 
@@ -171,7 +167,7 @@
     mNext = start(mCurrentPage);
 }
 
-void* LinearAllocator::alloc(size_t size) {
+void* LinearAllocator::allocImpl(size_t size) {
     size = ALIGN(size);
     if (size > mMaxAllocSize && !fitsInCurrentPage(size)) {
         ALOGV("Exceeded max size %zu > %zu", size, mMaxAllocSize);
@@ -196,7 +192,7 @@
                   "DestructorNode must have standard layout");
     static_assert(std::is_trivially_destructible<DestructorNode>::value,
                   "DestructorNode must be trivially destructable");
-    auto node = new (*this) DestructorNode();
+    auto node = new (allocImpl(sizeof(DestructorNode))) DestructorNode();
     node->dtor = dtor;
     node->addr = addr;
     node->next = mDtorList;
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index dcbc0dd..0a0e185 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -52,30 +52,36 @@
      * The lifetime of the returned buffers is tied to that of the LinearAllocator. If calling
      * delete() on an object stored in a buffer is needed, it should be overridden to use
      * rewindIfLastAlloc()
+     *
+     * Note that unlike create, for alloc the type is purely for compile-time error
+     * checking and does not affect size.
      */
-    void* alloc(size_t size);
+    template<class T>
+    void* alloc(size_t size) {
+        static_assert(std::is_trivially_destructible<T>::value,
+                "Error, type is non-trivial! did you mean to use create()?");
+        return allocImpl(size);
+    }
 
     /**
      * Allocates an instance of the template type with the given construction parameters
      * and adds it to the automatic destruction list.
      */
     template<class T, typename... Params>
-    T* create(Params... params) {
-        T* ret = new (*this) T(params...);
-        autoDestroy(ret);
+    T* create(Params&&... params) {
+        T* ret = new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...);
+        if (!std::is_trivially_destructible<T>::value) {
+            auto dtor = [](void* ret) { ((T*)ret)->~T(); };
+            addToDestructionList(dtor, ret);
+        }
         return ret;
     }
 
-    /**
-     * Adds the pointer to the tracking list to have its destructor called
-     * when the LinearAllocator is destroyed.
-     */
-    template<class T>
-    void autoDestroy(T* addr) {
-        if (!std::is_trivially_destructible<T>::value) {
-            auto dtor = [](void* addr) { ((T*)addr)->~T(); };
-            addToDestructionList(dtor, addr);
-        }
+    template<class T, typename... Params>
+    T* create_trivial(Params&&... params) {
+        static_assert(std::is_trivially_destructible<T>::value,
+                "Error, called create_trivial on a non-trivial type");
+        return new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...);
     }
 
     /**
@@ -114,6 +120,8 @@
         DestructorNode* next = nullptr;
     };
 
+    void* allocImpl(size_t size);
+
     void addToDestructionList(Destructor, void* addr);
     void runDestructorFor(void* addr);
     Page* newPage(size_t pageSize);
@@ -159,7 +167,7 @@
             : linearAllocator(other.linearAllocator) {}
 
     T* allocate(size_t num, const void* = 0) {
-        return (T*)(linearAllocator.alloc(num * sizeof(T)));
+        return (T*)(linearAllocator.alloc<void*>(num * sizeof(T)));
     }
 
     void deallocate(pointer p, size_t num) {
@@ -187,6 +195,4 @@
 }; // namespace uirenderer
 }; // namespace android
 
-void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la);
-
 #endif // ANDROID_LINEARALLOCATOR_H
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
index 127bddd..c23c745 100644
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
@@ -20,11 +20,31 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
 
-    <com.android.systemui.qs.PageIndicator
-        android:id="@+id/page_indicator"
-        android:layout_width="match_parent"
+    <FrameLayout
+        android:id="@+id/page_decor"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal|bottom"
-        android:gravity="center" />
+        android:layout_gravity="bottom">
+
+        <com.android.systemui.qs.PageIndicator
+            android:id="@+id/page_indicator"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:gravity="center" />
+
+        <TextView
+            android:id="@android:id/edit"
+            style="@style/QSBorderlessButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:minWidth="88dp"
+            android:textAppearance="@style/TextAppearance.QS.DetailButton"
+            android:textColor="#4DFFFFFF"
+            android:focusable="true"
+            android:text="@string/qs_edit" />
+
+    </FrameLayout>
 
 </com.android.systemui.qs.PagedTileLayout>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index bb37b83..45236a0 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -18,13 +18,16 @@
         android:id="@+id/quick_settings_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@drawable/qs_background_primary"
-        android:paddingBottom="8dp"
-        android:elevation="2dp">
+        android:background="@drawable/qs_background_primary">
 
     <com.android.systemui.qs.QSPanel
             android:id="@+id/quick_settings_panel"
             android:background="#0000"
+            android:layout_marginTop="@dimen/status_bar_header_height"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:paddingBottom="8dp" />
+
+    <include layout="@layout/quick_status_bar_expanded_header" />
+
 </com.android.systemui.qs.QSContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 89abe2d..289b1d9 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -39,32 +39,11 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
-        <com.android.systemui.statusbar.phone.ObservableScrollView
-            android:id="@+id/scroll_view"
+        <include
+            layout="@layout/qs_panel"
             android:layout_width="@dimen/notification_panel_width"
-            android:layout_height="match_parent"
-            android:layout_gravity="@integer/notification_panel_layout_gravity"
-            android:scrollbars="none"
-            android:overScrollMode="never"
-            android:fillViewport="true">
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="vertical">
-                <include
-                    layout="@layout/qs_panel"
-                    android:layout_marginTop="@dimen/status_bar_header_height_expanded"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-
-                <!-- A view to reserve space for the collapsed stack -->
-                <!-- Layout height: notification_min_height + bottom_stack_peek_amount -->
-                <View
-                    android:id="@+id/reserve_notification_space"
-                    android:layout_height="@dimen/min_stack_height"
-                    android:layout_width="match_parent" />
-            </LinearLayout>
-        </com.android.systemui.statusbar.phone.ObservableScrollView>
+            android:layout_height="wrap_content"
+            android:layout_gravity="@integer/notification_panel_layout_gravity" />
 
         <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
@@ -90,12 +69,6 @@
             layout="@layout/keyguard_bottom_area"
             android:visibility="gone" />
 
-    <ViewStub
-        android:id="@+id/status_bar_header"
-        android:layout_width="@dimen/notification_panel_width"
-        android:layout_height="@dimen/status_bar_header_height"
-        android:layout_gravity="@integer/notification_panel_layout_gravity" />
-
     <com.android.systemui.statusbar.AlphaOptimizedView
         android:id="@+id/qs_navbar_scrim"
         android:layout_height="96dp"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ff9be1..5d4789a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1405,4 +1405,7 @@
     <!-- Label for area where tiles can be dragged out of [CHAR LIMIT=60] -->
     <string name="drag_to_add_tiles">Drag to add tiles</string>
 
+    <!-- Button to edit the tile ordering of quick settings [CHAR LIMIT=60] -->
+    <string name="qs_edit">Edit</string>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index d723367..8e9857d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -6,7 +6,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-
 import com.android.internal.widget.PagerAdapter;
 import com.android.internal.widget.ViewPager;
 import com.android.systemui.R;
@@ -27,6 +26,7 @@
     private PageIndicator mPageIndicator;
 
     private int mNumPages;
+    private View mDecorGroup;
 
     public PagedTileLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -55,7 +55,8 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator);
-        ((LayoutParams) mPageIndicator.getLayoutParams()).isDecor = true;
+        mDecorGroup = findViewById(R.id.page_decor);
+        ((LayoutParams) mDecorGroup.getLayoutParams()).isDecor = true;
 
         mPages.add((TilePage) LayoutInflater.from(mContext)
                 .inflate(R.layout.qs_paged_page, this, false));
@@ -137,7 +138,7 @@
                 maxHeight = height;
             }
         }
-        setMeasuredDimension(getMeasuredWidth(), maxHeight + mPageIndicator.getMeasuredHeight());
+        setMeasuredDimension(getMeasuredWidth(), maxHeight + mDecorGroup.getMeasuredHeight());
     }
 
     private final Runnable mDistribute = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index cfe8d07..32eeb07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -16,19 +16,38 @@
 
 package com.android.systemui.qs;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
-
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.BaseStatusBarHeader;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
- * Wrapper view with background which contains {@link QSPanel}
+ * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader}
+ *
+ * Also manages animations for the QS Header and Panel.
  */
 public class QSContainer extends FrameLayout {
+    private static final String TAG = "QSContainer";
+    private static final boolean DEBUG = false;
 
     private int mHeightOverride = -1;
     private QSPanel mQSPanel;
+    protected BaseStatusBarHeader mHeader;
+    private float mQsExpansion;
+    private boolean mQsExpanded;
+    private boolean mHeaderAnimating;
+    private boolean mKeyguardShowing;
+    private boolean mStackScrollerOverscrolling;
+
+    private long mDelay;
 
     public QSContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -38,6 +57,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mQSPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+        mHeader = (BaseStatusBarHeader) findViewById(R.id.header);
     }
 
     @Override
@@ -63,14 +83,132 @@
      */
     public int getDesiredHeight() {
         if (mQSPanel.isClosingDetail()) {
-            return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom();
+            return mQSPanel.getGridHeight() + mHeader.getCollapsedHeight() + getPaddingBottom();
         } else {
             return getMeasuredHeight();
         }
     }
 
     private void updateBottom() {
-        int height = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
+        int height = (int) (mQsExpansion * (heightOverride - mHeader.getCollapsedHeight()))
+                + mHeader.getCollapsedHeight();
         setBottom(getTop() + height);
     }
+
+    private void updateQsState() {
+        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
+        mQSPanel.setExpanded(mQsExpanded);
+        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
+                ? View.VISIBLE
+                : View.INVISIBLE);
+        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
+                || (mQsExpanded && !mStackScrollerOverscrolling));
+        mQSPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    public BaseStatusBarHeader getHeader() {
+        return mHeader;
+    }
+
+    public QSPanel getQsPanel() {
+        return mQSPanel;
+    }
+
+    public void setHeaderClickable(boolean clickable) {
+        if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
+        mHeader.setClickable(clickable);
+    }
+
+    public void setExpanded(boolean expanded) {
+        if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
+        mQsExpanded = expanded;
+        updateQsState();
+    }
+
+    public void setKeyguardShowing(boolean keyguardShowing) {
+        if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
+        mKeyguardShowing = keyguardShowing;
+        updateQsState();
+    }
+
+    public void setOverscrolling(boolean stackScrollerOverscrolling) {
+        if (DEBUG) Log.d(TAG, "setOverscrolling " + stackScrollerOverscrolling);
+        mStackScrollerOverscrolling = stackScrollerOverscrolling;
+        updateQsState();
+    }
+
+    public void setListening(boolean listening) {
+        if (DEBUG) Log.d(TAG, "setListening " + listening);
+        mQSPanel.setListening(listening);
+        mHeader.setListening(listening);
+    }
+
+    public void setQsExpansion(float expansion, float headerTranslation) {
+        if (DEBUG) Log.d(TAG, "setQSExpansion " + expansion + " " + headerTranslation);
+        mQsExpansion = expansion;
+        final float translationScaleY = expansion - 1;
+        if (!mHeaderAnimating) {
+            setTranslationY(mKeyguardShowing ? (translationScaleY * mHeader.getHeight())
+                    : headerTranslation);
+        }
+        mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
+        mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
+        updateBottom();
+    }
+
+    public void animateHeaderSlidingIn(long delay) {
+        if (DEBUG) Log.d(TAG, "animateHeaderSlidingIn");
+        // If the QS is already expanded we don't need to slide in the header as it's already
+        // visible.
+        if (!mQsExpanded) {
+            mHeaderAnimating = true;
+            mDelay = delay;
+            getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
+        }
+    }
+
+    public void animateHeaderSlidingOut() {
+        if (DEBUG) Log.d(TAG, "animateHeaderSlidingOut");
+        mHeaderAnimating = true;
+        animate().y(-mHeader.getHeight())
+                .setStartDelay(0)
+                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        animate().setListener(null);
+                        mHeaderAnimating = false;
+                        updateQsState();
+                    }
+                })
+                .start();
+    }
+
+    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
+            = new ViewTreeObserver.OnPreDrawListener() {
+        @Override
+        public boolean onPreDraw() {
+            getViewTreeObserver().removeOnPreDrawListener(this);
+            animate()
+                    .translationY(0f)
+                    .setStartDelay(mDelay)
+                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .setListener(mAnimateHeaderSlidingInListener)
+                    .start();
+            setY(-mHeader.getHeight());
+            return true;
+        }
+    };
+
+    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
+            = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mHeaderAnimating = false;
+            updateQsState();
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1961860..4ffa527 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -117,6 +117,17 @@
         mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
                 R.layout.qs_paged_tile_layout, mQsContainer, false);
         mQsContainer.addView((View) mTileLayout);
+        findViewById(android.R.id.edit).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(final View v) {
+                mHost.startRunnableDismissingKeyguard(new Runnable() {
+                    @Override
+                    public void run() {
+                        showEdit(v);
+                    }
+                });
+            }
+        });
 
         mFooter = new QSFooter(this, context);
         mQsContainer.addView(mFooter.getView());
@@ -369,19 +380,7 @@
         final View.OnLongClickListener longClick = new View.OnLongClickListener() {
             @Override
             public boolean onLongClick(View v) {
-                if (mCustomizePanel != null) {
-                    if (!mCustomizePanel.isCustomizing()) {
-                        int[] loc = new int[2];
-                        getLocationInWindow(loc);
-                        int x = r.tileView.getLeft() + r.tileView.getWidth() / 2 + loc[0];
-                        int y = r.tileView.getTop() + mTileLayout.getOffsetTop(r)
-                                + r.tileView.getHeight() / 2 + loc[1];
-                        mCustomizePanel.show(x, y);
-                    }
-                } else {
-                    r.tile.longClick();
-                }
-                return true;
+                return false;
             }
         };
         r.tileView.init(click, longClick);
@@ -395,6 +394,25 @@
         }
     }
 
+
+    private void showEdit(final View v) {
+        v.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mCustomizePanel != null) {
+                    if (!mCustomizePanel.isCustomizing()) {
+                        int[] loc = new int[2];
+                        v.getLocationInWindow(loc);
+                        int x = loc[0];
+                        int y = loc[1];
+                        mCustomizePanel.show(x, y);
+                    }
+                }
+
+            }
+        });
+    }
+
     protected void onTileClick(QSTile<?> tile) {
         tile.click();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index f822bd5..8f0f51f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
@@ -35,13 +34,11 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 import android.widget.TextView;
-
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.DejankUtils;
@@ -51,7 +48,6 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.qs.QSContainer;
-import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -91,13 +87,10 @@
     public static final long DOZE_ANIMATION_DURATION = 700;
 
     private KeyguardAffordanceHelper mAfforanceHelper;
-    protected BaseStatusBarHeader mHeader;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private QSContainer mQsContainer;
-    private QSPanel mQsPanel;
     private KeyguardStatusView mKeyguardStatusView;
-    private ObservableScrollView mScrollView;
     private TextView mClockView;
     private View mReserveNotificationSpace;
     private View mQsNavbarScrim;
@@ -168,15 +161,12 @@
      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
      * need to take this into account in our panel height calculation.
      */
-    private int mScrollYOverride = -1;
     private boolean mQsAnimatorExpand;
     private boolean mIsLaunchTransitionFinished;
     private boolean mIsLaunchTransitionRunning;
     private Runnable mLaunchAnimationEndRunnable;
     private boolean mOnlyAffordanceInThisMotion;
     private boolean mKeyguardStatusViewAnimating;
-    private boolean mHeaderAnimating;
-    private ObjectAnimator mQsContainerAnimator;
     private ValueAnimator mQsSizeChangeAnimator;
 
     private boolean mShadeEmpty;
@@ -223,19 +213,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        ViewStub stub = (ViewStub) findViewById(R.id.status_bar_header);
-        stub.setLayoutResource(R.layout.quick_status_bar_expanded_header);
-        mHeader = (BaseStatusBarHeader) stub.inflate();
-        mHeader.setOnClickListener(this);
         mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
         mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
         mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
-        mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+        mQsContainer.getHeader().setOnClickListener(this);
         mClockView = (TextView) findViewById(R.id.clock_view);
-        mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
-        mScrollView.setListener(this);
-        mScrollView.setFocusable(false);
-        mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
         mNotificationContainerParent = (NotificationsQuickSettingsContainer)
                 findViewById(R.id.notification_container_parent);
         mNotificationStackScroller = (NotificationStackScrollLayout)
@@ -243,7 +225,7 @@
         mNotificationStackScroller.setOnHeightChangedListener(this);
         mNotificationStackScroller.setOverscrollTopChangedListener(this);
         mNotificationStackScroller.setOnEmptySpaceClickListener(this);
-        mNotificationStackScroller.setScrollView(mScrollView);
+        mNotificationStackScroller.setQsContainer(mQsContainer);
         mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
         mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
@@ -285,12 +267,12 @@
     public void updateResources() {
         int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
         int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mHeader.getLayoutParams();
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsContainer.getLayoutParams();
         if (lp.width != panelWidth) {
             lp.width = panelWidth;
             lp.gravity = panelGravity;
-            mHeader.setLayoutParams(lp);
-            mHeader.post(mUpdateHeader);
+            mQsContainer.setLayoutParams(lp);
+            mQsContainer.post(mUpdateHeader);
         }
 
         lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
@@ -299,13 +281,6 @@
             lp.gravity = panelGravity;
             mNotificationStackScroller.setLayoutParams(lp);
         }
-
-        lp = (FrameLayout.LayoutParams) mScrollView.getLayoutParams();
-        if (lp.width != panelWidth) {
-            lp.width = panelWidth;
-            lp.gravity = panelGravity;
-            mScrollView.setLayoutParams(lp);
-        }
     }
 
     @Override
@@ -318,8 +293,8 @@
 
         // Calculate quick setting heights.
         int oldMaxHeight = mQsMaxExpansionHeight;
-        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
-        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getDesiredHeight();
+        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQsContainer.getHeader().getHeight();
+        mQsMaxExpansionHeight = mQsContainer.getDesiredHeight();
         positionClockAndNotifications();
         if (mQsExpanded && mQsFullyExpanded) {
             mQsExpansionHeight = mQsMaxExpansionHeight;
@@ -361,7 +336,7 @@
                 requestScrollerTopPaddingUpdate(false /* animate */);
                 requestPanelHeightUpdate();
                 int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
-                mQsContainer.setHeightOverride(height - mHeader.getExpandedHeight());
+                mQsContainer.setHeightOverride(height);
             }
         });
         mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
@@ -381,7 +356,7 @@
         boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
         int stackScrollerPadding;
         if (mStatusBarState != StatusBarState.KEYGUARD) {
-            int bottom = mHeader.getCollapsedHeight();
+            int bottom = mQsContainer.getHeader().getHeight();
             stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
                     ? bottom + mQsPeekHeight
                     : mKeyguardStatusBar.getHeight();
@@ -485,7 +460,7 @@
 
     public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
         mQsExpansionEnabled = qsExpansionEnabled;
-        mHeader.setClickable(qsExpansionEnabled);
+        mQsContainer.setHeaderClickable(qsExpansionEnabled);
     }
 
     @Override
@@ -676,17 +651,6 @@
         }
     }
 
-    @Override
-    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-
-        // Block request when interacting with the scroll view so we can still intercept the
-        // scrolling when QS is expanded.
-        if (mScrollView.isHandlingTouchEvent()) {
-            return;
-        }
-        super.requestDisallowInterceptTouchEvent(disallowIntercept);
-    }
-
     private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
         float vel = getCurrentVelocity();
         final boolean expandsQs = flingExpandsQs(vel);
@@ -808,7 +772,7 @@
     }
 
     private boolean isInQsArea(float x, float y) {
-        return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) &&
+        return (x >= mQsContainer.getX() && x <= mQsContainer.getX() + mQsContainer.getWidth()) &&
                 (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
                 || y <= mQsContainer.getY() + mQsContainer.getHeight());
     }
@@ -948,7 +912,7 @@
             amount = 0f;
         }
         float rounded = amount >= 1f ? amount : 0f;
-        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
+        setOverScrolling(rounded != 0f && isRubberbanded);
         mQsExpansionFromOverscroll = rounded != 0f;
         mLastOverscroll = rounded;
         updateQsState();
@@ -964,12 +928,17 @@
                     @Override
                     public void run() {
                         mStackScrollerOverscrolling = false;
-                        mQsExpansionFromOverscroll = false;
+                        setOverScrolling(false);
                         updateQsState();
                     }
                 }, false /* isClick */);
     }
 
+    private void setOverScrolling(boolean overscrolling) {
+        mStackScrollerOverscrolling = overscrolling;
+        mQsContainer.setOverscrolling(overscrolling);
+    }
+
     private void onQsExpansionStarted() {
         onQsExpansionStarted(0);
     }
@@ -979,11 +948,7 @@
         cancelHeightAnimator();
 
         // Reset scroll position and apply that position to the expanded height.
-        float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
-        if (mScrollView.getScrollY() != 0) {
-            mScrollYOverride = mScrollView.getScrollY();
-        }
-        mScrollView.scrollTo(0, 0);
+        float height = mQsExpansionHeight - overscrollAmount;
         setQsExpansion(height);
         requestPanelHeightUpdate();
     }
@@ -995,9 +960,7 @@
             updateQsState();
             requestPanelHeightUpdate();
             mFalsingManager.setQsExpanded(expanded);
-            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
             mStatusBar.setQsExpanded(expanded);
-            mQsPanel.setExpanded(expanded);
             mNotificationContainerParent.setQsExpanded(expanded);
         }
     }
@@ -1011,15 +974,18 @@
 
         mStatusBarState = statusBarState;
         mKeyguardShowing = keyguardShowing;
+        mQsContainer.setKeyguardShowing(mKeyguardShowing);
 
         if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
                 && statusBarState == StatusBarState.SHADE_LOCKED)) {
             animateKeyguardStatusBarOut();
-            animateHeaderSlidingIn();
+            long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
+                    ? 0 : mStatusBar.calculateGoingToFullShadeDelay();
+            mQsContainer.animateHeaderSlidingIn(delay);
         } else if (oldState == StatusBarState.SHADE_LOCKED
                 && statusBarState == StatusBarState.KEYGUARD) {
             animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-            animateHeaderSlidingOut();
+            mQsContainer.animateHeaderSlidingOut();
         } else {
             mKeyguardStatusBar.setAlpha(1f);
             mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -1050,95 +1016,6 @@
         }
     };
 
-    private final Animator.AnimatorListener mAnimateHeaderSlidingInListener
-            = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mHeaderAnimating = false;
-            mQsContainerAnimator = null;
-            mQsContainer.removeOnLayoutChangeListener(mQsContainerAnimatorUpdater);
-        }
-    };
-
-    private final OnLayoutChangeListener mQsContainerAnimatorUpdater
-            = new OnLayoutChangeListener() {
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                int oldTop, int oldRight, int oldBottom) {
-            int oldHeight = oldBottom - oldTop;
-            int height = bottom - top;
-            if (height != oldHeight && mQsContainerAnimator != null) {
-                PropertyValuesHolder[] values = mQsContainerAnimator.getValues();
-                float newEndValue = mHeader.getCollapsedHeight() + mQsPeekHeight - height - top;
-                float newStartValue = -height - top;
-                values[0].setFloatValues(newStartValue, newEndValue);
-                mQsContainerAnimator.setCurrentPlayTime(mQsContainerAnimator.getCurrentPlayTime());
-            }
-        }
-    };
-
-    private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn
-            = new ViewTreeObserver.OnPreDrawListener() {
-        @Override
-        public boolean onPreDraw() {
-            getViewTreeObserver().removeOnPreDrawListener(this);
-            long delay = mStatusBarState == StatusBarState.SHADE_LOCKED
-                    ? 0
-                    : mStatusBar.calculateGoingToFullShadeDelay();
-            mHeader.setTranslationY(-mHeader.getCollapsedHeight() - mQsPeekHeight);
-            mHeader.animate()
-                    .translationY(0f)
-                    .setStartDelay(delay)
-                    .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .start();
-            mQsContainer.setY(-mQsContainer.getHeight());
-            mQsContainerAnimator = ObjectAnimator.ofFloat(mQsContainer, View.TRANSLATION_Y,
-                    mQsContainer.getTranslationY(),
-                    mHeader.getCollapsedHeight() + mQsPeekHeight - mQsContainer.getHeight()
-                            - mQsContainer.getTop());
-            mQsContainerAnimator.setStartDelay(delay);
-            mQsContainerAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
-            mQsContainerAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-            mQsContainerAnimator.addListener(mAnimateHeaderSlidingInListener);
-            mQsContainerAnimator.start();
-            mQsContainer.addOnLayoutChangeListener(mQsContainerAnimatorUpdater);
-            return true;
-        }
-    };
-
-    private void animateHeaderSlidingIn() {
-        // If the QS is already expanded we don't need to slide in the header as it's already
-        // visible.
-        if (!mQsExpanded) {
-            mHeaderAnimating = true;
-            getViewTreeObserver().addOnPreDrawListener(mStartHeaderSlidingIn);
-        }
-    }
-
-    private void animateHeaderSlidingOut() {
-        mHeaderAnimating = true;
-        mHeader.animate().y(-mHeader.getHeight())
-                .setStartDelay(0)
-                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .setListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mHeader.animate().setListener(null);
-                        mHeaderAnimating = false;
-                        updateQsState();
-                    }
-                })
-                .start();
-        mQsContainer.animate()
-                .y(-mQsContainer.getHeight())
-                .setStartDelay(0)
-                .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
-                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                .start();
-    }
-
     private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
         @Override
         public void run() {
@@ -1262,19 +1139,10 @@
     }
 
     private void updateQsState() {
-        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling || mHeaderAnimating;
-        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing || mHeaderAnimating)
-                ? View.VISIBLE
-                : View.INVISIBLE);
-        mHeader.setExpanded((mKeyguardShowing && !mHeaderAnimating)
-                || (mQsExpanded && !mStackScrollerOverscrolling));
+        mQsContainer.setExpanded(mQsExpanded);
         mNotificationStackScroller.setScrollingEnabled(
                 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
-        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
-        mQsContainer.setVisibility(
-                mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
-        mScrollView.setTouchEnabled(mQsExpanded);
         updateEmptyShadeView();
         mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded
                 && !mStackScrollerOverscrolling && mQsScrimEnabled
@@ -1298,11 +1166,10 @@
             }
         }
         mQsExpansionHeight = height;
-        mHeader.setExpansion(getHeaderExpansionFraction());
-        setQsTranslation(height);
+        updateQsExpansion();
         requestScrollerTopPaddingUpdate(false /* animate */);
         if (mKeyguardShowing) {
-            updateHeaderKeyguard();
+            updateHeaderKeyguardAlpha();
         }
         if (mStatusBarState == StatusBarState.SHADE_LOCKED
                 || mStatusBarState == StatusBarState.KEYGUARD) {
@@ -1329,6 +1196,10 @@
         }
     }
 
+    private void updateQsExpansion() {
+        mQsContainer.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation());
+    }
+
     private String getKeyguardOrLockScreenString() {
         if (mStatusBarState == StatusBarState.KEYGUARD) {
             return getContext().getString(R.string.accessibility_desc_lock_screen);
@@ -1337,23 +1208,6 @@
         }
     }
 
-    private float getHeaderExpansionFraction() {
-        if (!mKeyguardShowing) {
-            return getQsExpansionFraction();
-        } else {
-            return 1f;
-        }
-    }
-
-    private void setQsTranslation(float height) {
-        if (!mHeaderAnimating) {
-            mQsContainer.setY(height - mQsContainer.getDesiredHeight() + getHeaderTranslation());
-        }
-        if (mKeyguardShowing && !mHeaderAnimating) {
-            mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
-        }
-    }
-
     private float calculateQsTopPadding() {
         if (mKeyguardShowing
                 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
@@ -1372,7 +1226,7 @@
                     mQsMinExpansionHeight, max);
         } else if (mQsSizeChangeAnimator != null) {
             return (int) mQsSizeChangeAnimator.getAnimatedValue();
-        } else if (mKeyguardShowing && mScrollYOverride == -1) {
+        } else if (mKeyguardShowing) {
 
             // We can only do the smoother transition on Keyguard when we also are not collapsing
             // from a scrolled quick settings.
@@ -1386,7 +1240,6 @@
 
     private void requestScrollerTopPaddingUpdate(boolean animate) {
         mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
-                mScrollView.getScrollY(),
                 mAnimateNextTopPaddingChange || animate,
                 mKeyguardShowing
                         && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted));
@@ -1428,7 +1281,6 @@
             boolean isClick) {
         float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
         if (target == mQsExpansionHeight) {
-            mScrollYOverride = -1;
             if (onFinishRunnable != null) {
                 onFinishRunnable.run();
             }
@@ -1438,7 +1290,6 @@
         if (belowFalsingThreshold) {
             vel = 0;
         }
-        mScrollView.setBlockFlinging(true);
         ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
         if (isClick) {
             animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
@@ -1458,8 +1309,6 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mScrollView.setBlockFlinging(false);
-                mScrollYOverride = -1;
                 mQsExpansionAnimator = null;
                 if (onFinishRunnable != null) {
                     onFinishRunnable.run();
@@ -1478,11 +1327,11 @@
         if (!mQsExpansionEnabled || mCollapsedOnDown) {
             return false;
         }
-        View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
+        View header = mKeyguardShowing ? mKeyguardStatusBar : mQsContainer.getHeader();
         boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
                 && y >= header.getTop() && y <= header.getBottom();
         if (mQsExpanded) {
-            return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
+            return onHeader || (yDiff < 0 && isInQsArea(x, y));
         } else {
             return onHeader;
         }
@@ -1494,7 +1343,7 @@
             return mStatusBar.getBarState() == StatusBarState.KEYGUARD
                     || mNotificationStackScroller.isScrolledToBottom();
         } else {
-            return mScrollView.isScrolledToBottom();
+            return true;
         }
     }
 
@@ -1571,11 +1420,7 @@
      *         collapsing QS / the panel when QS was scrolled
      */
     private int getTempQsMaxExpansion() {
-        int qsTempMaxExpansion = mQsMaxExpansionHeight;
-        if (mScrollYOverride != -1) {
-            qsTempMaxExpansion -= mScrollYOverride;
-        }
-        return qsTempMaxExpansion;
+        return mQsMaxExpansionHeight;
     }
 
     private int calculatePanelHeightShade() {
@@ -1613,20 +1458,12 @@
                 + notificationHeight;
         if (totalHeight > mNotificationStackScroller.getHeight()) {
             float fullyCollapsedHeight = maxQsHeight
-                    + mNotificationStackScroller.getMinStackHeight()
-                    - getScrollViewScrollY();
+                    + mNotificationStackScroller.getMinStackHeight();
             totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
         }
         return (int) totalHeight;
     }
 
-    private int getScrollViewScrollY() {
-        if (mScrollYOverride != -1 && !mQsTracking) {
-            return mScrollYOverride;
-        } else {
-            return mScrollView.getScrollY();
-        }
-    }
     private void updateNotificationTranslucency() {
         float alpha = 1f;
         if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -1678,18 +1515,9 @@
      */
     private void updateHeader() {
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
-            updateHeaderKeyguard();
-        } else {
-            updateHeaderShade();
+            updateHeaderKeyguardAlpha();
         }
-
-    }
-
-    private void updateHeaderShade() {
-        if (!mHeaderAnimating) {
-            mHeader.setTranslationY(getHeaderTranslation());
-        }
-        setQsTranslation(mQsExpansionHeight);
+        updateQsExpansion();
     }
 
     private float getHeaderTranslation() {
@@ -1744,11 +1572,6 @@
                 && !mDozing ? VISIBLE : INVISIBLE);
     }
 
-    private void updateHeaderKeyguard() {
-        updateHeaderKeyguardAlpha();
-        setQsTranslation(mQsExpansionHeight);
-    }
-
     private void updateKeyguardBottomAreaAlpha() {
         float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction());
         mKeyguardBottomArea.setAlpha(alpha);
@@ -1781,7 +1604,6 @@
         mNotificationStackScroller.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
         mIsExpanding = false;
-        mScrollYOverride = -1;
         if (isFullyCollapsed()) {
             DejankUtils.postAfterTraversal(new Runnable() {
                 @Override
@@ -1811,9 +1633,8 @@
     }
 
     private void setListening(boolean listening) {
-        mHeader.setListening(listening);
+        mQsContainer.setListening(listening);
         mKeyguardStatusBar.setListening(listening);
-        mQsPanel.setListening(listening);
     }
 
     @Override
@@ -1931,7 +1752,7 @@
 
     @Override
     public void onClick(View v) {
-        if (v == mHeader) {
+        if (v == mQsContainer.getHeader()) {
             onQsExpansionStarted();
             if (mQsExpanded) {
                 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */);
@@ -2149,24 +1970,16 @@
         return mConflictingQsExpansionGesture && mQsExpanded;
     }
 
-    public void notifyVisibleChildrenChanged() {
-        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
-            mReserveNotificationSpace.setVisibility(View.VISIBLE);
-        } else {
-            mReserveNotificationSpace.setVisibility(View.GONE);
-        }
-    }
-
     public boolean isQsExpanded() {
         return mQsExpanded;
     }
 
     public boolean isQsDetailShowing() {
-        return mQsPanel.isShowingDetail();
+        return mQsContainer.getQsPanel().isShowingDetail();
     }
 
     public void closeQsDetail() {
-        mQsPanel.closeDetail();
+        mQsContainer.getQsPanel().closeDetail();
     }
 
     @Override
@@ -2254,7 +2067,7 @@
     private final Runnable mUpdateHeader = new Runnable() {
         @Override
         public void run() {
-            mHeader.updateEverything();
+            mQsContainer.getHeader().updateEverything();
         }
     };
 
@@ -2400,8 +2213,7 @@
 
     protected void setVerticalPanelTranslation(float translation) {
         mNotificationStackScroller.setTranslationX(translation);
-        mScrollView.setTranslationX(translation);
-        mHeader.setTranslationX(translation);
+        mQsContainer.setTranslationX(translation);
     }
 
     private void updateStackHeight(float stackHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index fd28b09..7cc720d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -33,7 +33,7 @@
 public class NotificationsQuickSettingsContainer extends FrameLayout
         implements ViewStub.OnInflateListener {
 
-    private View mScrollView;
+    private View mQsContainer;
     private View mUserSwitcher;
     private View mStackScroller;
     private View mKeyguardStatusBar;
@@ -47,7 +47,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mScrollView = findViewById(R.id.scroll_view);
+        mQsContainer = findViewById(R.id.quick_settings_container);
         mStackScroller = findViewById(R.id.notification_stack_scroller);
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
         ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher);
@@ -58,7 +58,7 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        reloadWidth(mScrollView);
+        reloadWidth(mQsContainer);
         reloadWidth(mStackScroller);
     }
 
@@ -80,11 +80,11 @@
         boolean userSwitcherVisible = mInflated && mUserSwitcher.getVisibility() == View.VISIBLE;
         boolean statusBarVisible = mKeyguardStatusBar.getVisibility() == View.VISIBLE;
 
-        View stackQsTop = mQsExpanded ? mStackScroller : mScrollView;
-        View stackQsBottom = !mQsExpanded ? mStackScroller : mScrollView;
+        View stackQsTop = mQsExpanded ? mStackScroller : mQsContainer;
+        View stackQsBottom = !mQsExpanded ? mStackScroller : mQsContainer;
         // Invert the order of the scroll view and user switcher such that the notifications receive
         // touches first but the panel gets drawn above.
-        if (child == mScrollView) {
+        if (child == mQsContainer) {
             return super.drawChild(canvas, userSwitcherVisible && statusBarVisible ? mUserSwitcher
                     : statusBarVisible ? mKeyguardStatusBar
                     : userSwitcherVisible ? mUserSwitcher
@@ -104,7 +104,7 @@
             return super.drawChild(canvas,
                     stackQsTop,
                     drawingTime);
-        }else {
+        } else {
             return super.drawChild(canvas, child, drawingTime);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 50a49a1..f382fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1600,12 +1600,6 @@
     }
 
     @Override
-    protected void updateRowStates() {
-        super.updateRowStates();
-        mNotificationPanel.notifyVisibleChildrenChanged();
-    }
-
-    @Override
     protected void setAreThereNotifications() {
 
         if (SPEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 49e9c3d..a75ac82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -213,7 +213,6 @@
      * animating.
      */
     private boolean mOnlyScrollingInThisMotion;
-    private ViewGroup mScrollView;
     private boolean mInterceptDelegateEnabled;
     private boolean mDelegateToScrollView;
     private boolean mDisallowScrollingInThisMotion;
@@ -281,6 +280,7 @@
             setDimAmount((Float) animation.getAnimatedValue());
         }
     };
+    private ViewGroup mQsContainer;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -630,12 +630,8 @@
         mLongPressListener = listener;
     }
 
-    public void setScrollView(ViewGroup scrollView) {
-        mScrollView = scrollView;
-    }
-
-    public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
-        mInterceptDelegateEnabled = interceptDelegateEnabled;
+    public void setQsContainer(ViewGroup qsContainer) {
+        mQsContainer = qsContainer;
     }
 
     public void onChildDismissed(View v) {
@@ -883,13 +879,6 @@
     public boolean onTouchEvent(MotionEvent ev) {
         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
-        if (mDelegateToScrollView) {
-            if (isCancelOrUp) {
-                mDelegateToScrollView = false;
-            }
-            transformTouchEvent(ev, this, mScrollView);
-            return mScrollView.onTouchEvent(ev);
-        }
         handleEmptySpaceClick(ev);
         boolean expandWantsIt = false;
         if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
@@ -929,6 +918,9 @@
         if (!isScrollingEnabled()) {
             return false;
         }
+        if (ev.getY() < mQsContainer.getBottom()) {
+            return false;
+        }
         initVelocityTrackerIfNotExists();
         mVelocityTracker.addMovement(ev);
 
@@ -1776,15 +1768,13 @@
      * account.
      *
      * @param qsHeight the top padding imposed by the quick settings panel
-     * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
-     *                container
      * @param animate whether to animate the change
      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
      *                               {@code qsHeight} is the final top padding
      */
-    public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
+    public void updateTopPadding(float qsHeight, boolean animate,
             boolean ignoreIntrinsicPadding) {
-        float start = qsHeight - scrollY;
+        float start = qsHeight;
         float stackHeight = getHeight() - start;
         int minStackHeight = getMinStackHeight();
         if (stackHeight <= minStackHeight) {
@@ -1867,15 +1857,6 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (mInterceptDelegateEnabled) {
-            transformTouchEvent(ev, this, mScrollView);
-            if (mScrollView.onInterceptTouchEvent(ev)) {
-                mDelegateToScrollView = true;
-                removeLongPressCallback();
-                return true;
-            }
-            transformTouchEvent(ev, mScrollView, this);
-        }
         initDownStates(ev);
         handleEmptySpaceClick(ev);
         boolean expandWantsIt = false;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 830b0f2..7149065 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1044,7 +1044,7 @@
 
         // If Encrypted file systems is enabled or disabled, this call will return the
         // correct directory.
-        mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup");
+        mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
         mBaseStateDir.mkdirs();
         if (!SELinux.restorecon(mBaseStateDir)) {
             Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index bbf881b..e745263 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -54,7 +54,7 @@
 
     public Trampoline(Context context) {
         mContext = context;
-        File dir = new File(Environment.getSecureDataDirectory(), "backup");
+        File dir = new File(Environment.getDataDirectory(), "backup");
         dir.mkdirs();
         mSuppressFile = new File(dir, BACKUP_SUPPRESS_FILENAME);
         mGlobalDisable = SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index cbd477a..d09edc8 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -1473,7 +1473,7 @@
         }
 
         mSettingsFile = new AtomicFile(
-                new File(Environment.getSystemSecureDirectory(), "storage.xml"));
+                new File(Environment.getDataSystemDirectory(), "storage.xml"));
 
         synchronized (mLock) {
             readSettingsLocked();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 2683be6..ca1e371 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3809,7 +3809,7 @@
     }
 
     private static String getDatabaseName(int userId) {
-        File systemDir = Environment.getSystemSecureDirectory();
+        File systemDir = Environment.getDataSystemDirectory();
         File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME);
         if (userId == 0) {
             // Migrate old file, if it exists, to the new location.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8560a9e..75dabc96 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1127,7 +1127,8 @@
                 if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
                 prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
             } else if (prev.app != null) {
-                if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending stop: " + prev);
+                if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev
+                        + " wasStopping=" + wasStopping + " visible=" + prev.visible);
                 if (mStackSupervisor.mWaitingVisibleActivities.remove(prev)) {
                     if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG_PAUSE,
                             "Complete pause, no longer waiting: " + prev);
@@ -1142,7 +1143,8 @@
                     // We can't clobber it, because the stop confirmation will not be handled.
                     // We don't need to schedule another stop, we only need to let it happen.
                     prev.state = ActivityState.STOPPING;
-                } else if (!hasVisibleBehindActivity() || mService.isSleepingOrShuttingDown()) {
+                } else if ((!prev.visible && !hasVisibleBehindActivity())
+                        || mService.isSleepingOrShuttingDown()) {
                     // If we were visible then resumeTopActivities will release resources before
                     // stopping.
                     addToStopping(prev);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 26108a3..e90b5db 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1938,9 +1938,6 @@
         }
         final boolean updated = stack.ensureActivityConfigurationLocked(r, 0,
                 preserveWindows);
-        // And we need to make sure at this point that all other activities
-        // are made visible with the correct configuration.
-        ensureActivitiesVisibleLocked(r, 0, preserveWindows);
         if (!updated) {
             resumeFocusedStackTopActivityLocked();
         }
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index f8e3e48..965054f 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -438,9 +438,7 @@
         if (sSyncStorageEngine != null) {
             return;
         }
-        // This call will return the correct directory whether Encrypted File Systems is
-        // enabled or not.
-        File dataDir = Environment.getSecureDataDirectory();
+        File dataDir = Environment.getDataDirectory();
         sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
     }
 
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index 62114cd..7e19c66 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -52,7 +52,7 @@
     static final String TAG = "IntentFirewall";
 
     // e.g. /data/system/ifw or /data/secure/system/ifw
-    private static final File RULES_DIR = new File(Environment.getSystemSecureDirectory(), "ifw");
+    private static final File RULES_DIR = new File(Environment.getDataSystemDirectory(), "ifw");
 
     private static final int LOG_PACKAGES_MAX_LENGTH = 150;
     private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 29d52c1..0d6e3e5 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -53,6 +53,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Objects;
@@ -547,14 +548,14 @@
                     loadComponentNamesFromSetting(mConfig.secureSettingName, userIds[i]));
         }
 
-        final ArrayList<ManagedServiceInfo> toRemove = new ArrayList<>();
-        final SparseArray<ArrayList<ComponentName>> toAdd = new SparseArray<>();
+        final ArrayList<ManagedServiceInfo> removableBoundServices = new ArrayList<>();
+        final SparseArray<Set<ComponentName>> toAdd = new SparseArray<>();
 
         synchronized (mMutex) {
-            // Unbind automatically bound services, retain system services.
+            // Potentially unbind automatically bound services, retain system services.
             for (ManagedServiceInfo service : mServices) {
                 if (!service.isSystem && !service.isGuest(this)) {
-                    toRemove.add(service);
+                    removableBoundServices.add(service);
                 }
             }
 
@@ -565,11 +566,11 @@
                 // decode the list of components
                 final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]);
                 if (null == userComponents) {
-                    toAdd.put(userIds[i], new ArrayList<ComponentName>());
+                    toAdd.put(userIds[i], new HashSet<ComponentName>());
                     continue;
                 }
 
-                final ArrayList<ComponentName> add = new ArrayList<>(userComponents);
+                final Set<ComponentName> add = new HashSet<>(userComponents);
 
                 // Remove components from disabled categories so that those services aren't run.
                 for (Entry<String, Boolean> e : mCategoryEnabled.entrySet()) {
@@ -594,19 +595,26 @@
             mEnabledServicesPackageNames = newPackages;
         }
 
-        for (ManagedServiceInfo info : toRemove) {
+        for (ManagedServiceInfo info : removableBoundServices) {
             final ComponentName component = info.component;
             final int oldUser = info.userid;
-            Slog.v(TAG, "disabling " + getCaption() + " for user "
-                    + oldUser + ": " + component);
-            unregisterService(component, info.userid);
+            final Set<ComponentName> allowedComponents = toAdd.get(info.userid);
+            if (allowedComponents != null) {
+                if (allowedComponents.contains(component)) {
+                    // Already bound, don't need to bind again.
+                    allowedComponents.remove(component);
+                } else {
+                    // No longer allowed to be bound.
+                    Slog.v(TAG, "disabling " + getCaption() + " for user "
+                            + oldUser + ": " + component);
+                    unregisterService(component, oldUser);
+                }
+            }
         }
 
         for (int i = 0; i < nUserIds; ++i) {
-            final ArrayList<ComponentName> add = toAdd.get(userIds[i]);
-            final int N = add.size();
-            for (int j = 0; j < N; j++) {
-                final ComponentName component = add.get(j);
+            final Set<ComponentName> add = toAdd.get(userIds[i]);
+            for (ComponentName component : add) {
                 Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": "
                         + component);
                 registerService(component, userIds[i]);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 23a58d0..928c19f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -212,8 +212,8 @@
         mCallbacks = new Callbacks(mInstallThread.getLooper());
 
         mSessionsFile = new AtomicFile(
-                new File(Environment.getSystemSecureDirectory(), "install_sessions.xml"));
-        mSessionsDir = new File(Environment.getSystemSecureDirectory(), "install_sessions");
+                new File(Environment.getDataSystemDirectory(), "install_sessions.xml"));
+        mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
         mSessionsDir.mkdirs();
 
         synchronized (mSessions) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e47d514..31d16cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12724,9 +12724,6 @@
         String pkgName = pkg.packageName;
 
         if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
-        // TODO: b/23350563
-        final boolean dataDirExists = Environment
-                .getDataUserPackageDirectory(volumeUuid, UserHandle.USER_SYSTEM, pkgName).exists();
 
         synchronized(mPackages) {
             if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -12752,18 +12749,15 @@
                     System.currentTimeMillis(), user);
 
             updateSettingsLI(newPackage, installerPackageName, null, null, res, user);
-            prepareAppDataAfterInstall(newPackage);
 
-            // delete the partially installed application. the data directory will have to be
-            // restored if it was already existing
-            if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
-                // remove package from internal structures.  Note that we want deletePackageX to
-                // delete the package data and cache directories that it created in
-                // scanPackageLocked, unless those directories existed before we even tried to
-                // install.
+            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                prepareAppDataAfterInstall(newPackage);
+
+            } else {
+                // Remove package from internal structures, but keep around any
+                // data that might have already existed
                 deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
-                        dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
-                                res.removedInfo, true, null);
+                        PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);
             }
 
         } catch (PackageManagerException e) {
@@ -17423,8 +17417,9 @@
      * recycled.
      */
     private void reconcileUsers(String volumeUuid) {
+        // TODO: also reconcile DE directories
         final File[] files = FileUtils
-                .listFilesOrEmpty(Environment.getDataUserDirectory(volumeUuid));
+                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid));
         for (File file : files) {
             if (!file.isDirectory()) continue;
 
@@ -17553,8 +17548,8 @@
         Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
                 + Integer.toHexString(flags));
 
-        final File ceDir = Environment.getDataUserCredentialEncryptedDirectory(volumeUuid, userId);
-        final File deDir = Environment.getDataUserDeviceEncryptedDirectory(volumeUuid, userId);
+        final File ceDir = Environment.getDataUserCeDirectory(volumeUuid, userId);
+        final File deDir = Environment.getDataUserDeDirectory(volumeUuid, userId);
 
         boolean restoreconNeeded = false;
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index f57f75f..4b355de62 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -155,7 +155,7 @@
      */
     public static boolean isValidRestriction(@NonNull String restriction) {
         if (!USER_RESTRICTIONS.contains(restriction)) {
-            Slog.wtf(TAG, "Unknown restriction: " + restriction);
+            Slog.e(TAG, "Unknown restriction: " + restriction);
             return false;
         }
         return true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eacf11f..2ed96c6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -116,6 +116,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -274,6 +275,8 @@
     private static final int PROFILE_KEYGUARD_FEATURES =
             PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER | PROFILE_KEYGUARD_FEATURES_AFFECT_PROFILE;
 
+    private static final int DEVICE_ADMIN_DEACTIVATE_TIMEOUT = 10000;
+
     final Context mContext;
     final Injector mInjector;
     final IPackageManager mIPackageManager;
@@ -281,6 +284,13 @@
     final UserManagerInternal mUserManagerInternal;
     private final LockPatternUtils mLockPatternUtils;
 
+    /**
+     * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
+     * is requested for user u.
+     */
+    private final Set<Pair<String, Integer>> mPackagesToRemove =
+            new ArraySet<Pair<String, Integer>>();
+
     final LocalService mLocalService;
 
     // Stores and loads state on device and profile owners.
@@ -2015,35 +2025,19 @@
         final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
         if (admin != null) {
             getUserData(userHandle).mRemovingAdmins.add(adminReceiver);
-
             sendAdminCommandLocked(admin,
                     DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED,
                     new BroadcastReceiver() {
                         @Override
                         public void onReceive(Context context, Intent intent) {
-                            synchronized (DevicePolicyManagerService.this) {
-                                int userHandle = admin.getUserHandle().getIdentifier();
-                                DevicePolicyData policy = getUserData(userHandle);
-                                boolean doProxyCleanup = admin.info.usesPolicy(
-                                        DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
-                                policy.mAdminList.remove(admin);
-                                policy.mAdminMap.remove(adminReceiver);
-                                validatePasswordOwnerLocked(policy);
-                                if (doProxyCleanup) {
-                                    resetGlobalProxyLocked(getUserData(userHandle));
-                                }
-                                saveSettingsLocked(userHandle);
-                                updateMaximumTimeToLockLocked(userHandle);
-                                policy.mRemovingAdmins.remove(adminReceiver);
-                            }
-                            // The removed admin might have disabled camera, so update user
-                            // restrictions.
-                            pushUserRestrictions(userHandle);
+                            removeAdminArtifacts(adminReceiver, userHandle);
+                            removePackageIfRequired(adminReceiver.getPackageName(), userHandle);
                         }
                     });
         }
     }
 
+
     public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle,
             boolean throwForMissiongPermission) {
         if (!mHasFeature) {
@@ -8465,4 +8459,131 @@
         List<SecurityEvent> logs = mSecurityLogMonitor.retrieveLogs();
         return logs != null ? new ParceledListSlice<SecurityEvent>(logs) : null;
     }
+
+    private void enforceCanManageDeviceAdmin() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS,
+                null);
+    }
+
+    @Override
+    public boolean isUninstallInQueue(final String packageName) {
+        enforceCanManageDeviceAdmin();
+        final int userId = mInjector.userHandleGetCallingUserId();
+        Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+        synchronized (this) {
+            return mPackagesToRemove.contains(packageUserPair);
+        }
+    }
+
+    @Override
+    public void uninstallPackageWithActiveAdmins(final String packageName) {
+        enforceCanManageDeviceAdmin();
+        Preconditions.checkArgument(!TextUtils.isEmpty(packageName));
+
+        final int userId = mInjector.userHandleGetCallingUserId();
+
+        final ComponentName profileOwner = getProfileOwner(userId);
+        if (profileOwner != null && packageName.equals(profileOwner.getPackageName())) {
+            throw new IllegalArgumentException("Cannot uninstall a package with a profile owner");
+        }
+
+        final ComponentName deviceOwner = getDeviceOwnerComponent(/* callingUserOnly= */ false);
+        if (getDeviceOwnerUserId() == userId && deviceOwner != null
+                && packageName.equals(deviceOwner.getPackageName())) {
+            throw new IllegalArgumentException("Cannot uninstall a package with a device owner");
+        }
+
+        final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+        synchronized (this) {
+            mPackagesToRemove.add(packageUserPair);
+        }
+
+        final List<ComponentName> activeAdminsList = getActiveAdmins(userId);
+        if (activeAdminsList == null || activeAdminsList.size() == 0) {
+            startUninstallIntent(packageName, userId);
+            return;
+        }
+
+        for (ComponentName activeAdmin : activeAdminsList) {
+            if (packageName.equals(activeAdmin.getPackageName())) {
+                removeActiveAdmin(activeAdmin, userId);
+            }
+        }
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                for (ComponentName activeAdmin : activeAdminsList) {
+                    removeAdminArtifacts(activeAdmin, userId);
+                }
+                startUninstallIntent(packageName, userId);
+            }
+        }, DEVICE_ADMIN_DEACTIVATE_TIMEOUT); // Start uninstall after timeout anyway.
+    }
+
+    private void removePackageIfRequired(final String packageName, final int userId) {
+        if (!packageHasActiveAdmins(packageName, userId)) {
+            // Will not do anything if uninstall was not requested or was already started.
+            startUninstallIntent(packageName, userId);
+        }
+    }
+
+    private void startUninstallIntent(final String packageName, final int userId) {
+        final Pair<String, Integer> packageUserPair = new Pair<>(packageName, userId);
+        synchronized (this) {
+            if (!mPackagesToRemove.contains(packageUserPair)) {
+                // Do nothing if uninstall was not requested or was already started.
+                return;
+            }
+            mPackagesToRemove.remove(packageUserPair);
+        }
+        try {
+            if (mInjector.getIPackageManager().getPackageInfo(packageName, 0, userId) == null) {
+                // Package does not exist. Nothing to do.
+                return;
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Failure talking to PackageManager while getting package info");
+        }
+
+        try { // force stop the package before uninstalling
+            mInjector.getIActivityManager().forceStopPackage(packageName, userId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Failure talking to ActivityManager while force stopping package");
+        }
+        final Uri packageURI = Uri.parse("package:" + packageName);
+        final Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
+        uninstallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivityAsUser(uninstallIntent, UserHandle.of(userId));
+    }
+
+    /**
+     * Removes the admin from the policy. Ideally called after the admin's
+     * {@link DeviceAdminReceiver#onDisabled(Context, Intent)} has been successfully completed.
+     *
+     * @param adminReceiver The admin to remove
+     * @param userHandle The user for which this admin has to be removed.
+     */
+    private void removeAdminArtifacts(final ComponentName adminReceiver, final int userHandle) {
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
+            if (admin == null) {
+                return;
+            }
+            final DevicePolicyData policy = getUserData(userHandle);
+            final boolean doProxyCleanup = admin.info.usesPolicy(
+                    DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
+            policy.mAdminList.remove(admin);
+            policy.mAdminMap.remove(adminReceiver);
+            validatePasswordOwnerLocked(policy);
+            if (doProxyCleanup) {
+                resetGlobalProxyLocked(policy);
+            }
+            saveSettingsLocked(userHandle);
+            updateMaximumTimeToLockLocked(userHandle);
+            policy.mRemovingAdmins.remove(adminReceiver);
+        }
+        // The removed admin might have disabled camera, so update user
+        // restrictions.
+        pushUserRestrictions(userHandle);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 880f810..68fd0f6 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -716,11 +716,11 @@
     }
 
     File getLegacyConfigFileWithTestOverride() {
-        return new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML_LEGACY);
+        return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML_LEGACY);
     }
 
     File getDeviceOwnerFileWithTestOverride() {
-        return new File(Environment.getSystemSecureDirectory(), DEVICE_OWNER_XML);
+        return new File(Environment.getDataSystemDirectory(), DEVICE_OWNER_XML);
     }
 
     File getProfileOwnerFileWithTestOverride(int userId) {
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 06b6ee7..027c325 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -92,13 +92,18 @@
          */
 
         // Implementations must call IpManager#completedPreDhcpAction().
+        // TODO: Remove this requirement, perhaps via some
+        // registerForPreDhcpAction()-style mechanism.
         public void onPreDhcpAction() {}
         public void onPostDhcpAction() {}
 
-        // TODO: Kill with fire once DHCP and static configuration are moved
-        // out of WifiStateMachine.
-        public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults) {}
-        public void onIPv4ProvisioningFailure() {}
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        public void onNewDhcpResults(DhcpResults dhcpResults) {}
 
         public void onProvisioningSuccess(LinkProperties newLp) {}
         public void onProvisioningFailure(LinkProperties newLp) {}
@@ -122,6 +127,7 @@
 
     private final Object mLock = new Object();
     private final State mStoppedState = new StoppedState();
+    private final State mStoppingState = new StoppingState();
     private final State mStartedState = new StartedState();
 
     private final Context mContext;
@@ -179,6 +185,8 @@
         // Super simple StateMachine.
         addState(mStoppedState);
         addState(mStartedState);
+        addState(mStoppingState);
+
         setInitialState(mStoppedState);
         setLogRecSize(MAX_LOG_RECORDS);
         super.start();
@@ -203,13 +211,11 @@
 
     public void startProvisioning(StaticIpConfiguration staticIpConfig) {
         getInterfaceIndex();
-
         sendMessage(CMD_START, staticIpConfig);
     }
 
     public void startProvisioning() {
         getInterfaceIndex();
-
         sendMessage(CMD_START);
     }
 
@@ -236,6 +242,42 @@
      * Internals.
      */
 
+    @Override
+    protected String getWhatToString(int what) {
+        // TODO: Investigate switching to reflection.
+        switch (what) {
+            case CMD_STOP:
+                return "CMD_STOP";
+            case CMD_START:
+                return "CMD_START";
+            case CMD_CONFIRM:
+                return "CMD_CONFIRM";
+            case EVENT_PRE_DHCP_ACTION_COMPLETE:
+                return "EVENT_PRE_DHCP_ACTION_COMPLETE";
+            case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                return "EVENT_NETLINK_LINKPROPERTIES_CHANGED";
+            case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+                return "DhcpStateMachine.CMD_PRE_DHCP_ACTION";
+            case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+                return "DhcpStateMachine.CMD_POST_DHCP_ACTION";
+            case DhcpStateMachine.CMD_ON_QUIT:
+                return "DhcpStateMachine.CMD_ON_QUIT";
+        }
+        return "UNKNOWN:" + Integer.toString(what);
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        final String logLine = String.format(
+                "iface{%s/%d} arg1{%d} arg2{%d} obj{%s}",
+                mInterfaceName, mInterfaceIndex,
+                msg.arg1, msg.arg2, Objects.toString(msg.obj));
+        if (VDBG) {
+            Log.d(TAG, getWhatToString(msg.what) + " " + logLine);
+        }
+        return logLine;
+    }
+
     private void getInterfaceIndex() {
         try {
             mInterfaceIndex = NetworkInterface.getByName(mInterfaceName).getIndex();
@@ -260,16 +302,93 @@
         }
     }
 
+    // For now: use WifiStateMachine's historical notion of provisioned.
+    private static boolean isProvisioned(LinkProperties lp) {
+        // For historical reasons, we should connect even if all we have is
+        // an IPv4 address and nothing else.
+        return lp.isProvisioned() || lp.hasIPv4Address();
+    }
+
+    // TODO: Investigate folding all this into the existing static function
+    // LinkProperties.compareProvisioning() or some other single function that
+    // takes two LinkProperties objects and returns a ProvisioningChange
+    // object that is a correct and complete assessment of what changed, taking
+    // account of the asymmetries described in the comments in this function.
+    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+    private static ProvisioningChange compareProvisioning(
+            LinkProperties oldLp, LinkProperties newLp) {
+        ProvisioningChange delta;
+
+        final boolean wasProvisioned = isProvisioned(oldLp);
+        final boolean isProvisioned = isProvisioned(newLp);
+
+        if (!wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.GAINED_PROVISIONING;
+        } else if (wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.STILL_PROVISIONED;
+        } else if (!wasProvisioned && !isProvisioned) {
+            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
+        } else {
+            // (wasProvisioned && !isProvisioned)
+            //
+            // Note that this is true even if we lose a configuration element
+            // (e.g., a default gateway) that would not be required to advance
+            // into provisioned state. This is intended: if we have a default
+            // router and we lose it, that's a sure sign of a problem, but if
+            // we connect to a network with no IPv4 DNS servers, we consider
+            // that to be a network without DNS servers and connect anyway.
+            //
+            // See the comment below.
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        // Additionally:
+        //
+        // Partial configurations (e.g., only an IPv4 address with no DNS
+        // servers and no default route) are accepted as long as DHCPv4
+        // succeeds. On such a network, isProvisioned() will always return
+        // false, because the configuration is not complete, but we want to
+        // connect anyway. It might be a disconnected network such as a
+        // Chromecast or a wireless printer, for example.
+        //
+        // Because on such a network isProvisioned() will always return false,
+        // delta will never be LOST_PROVISIONING. So check for loss of
+        // provisioning here too.
+        if ((oldLp.hasIPv4Address() && !newLp.hasIPv4Address()) ||
+                (oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned())) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        return delta;
+    }
+
+    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
+        switch (delta) {
+            case GAINED_PROVISIONING:
+                if (VDBG) { Log.d(TAG, "onProvisioningSuccess()"); }
+                mCallback.onProvisioningSuccess(newLp);
+                break;
+
+            case LOST_PROVISIONING:
+                if (VDBG) { Log.d(TAG, "onProvisioningFailure()"); }
+                mCallback.onProvisioningFailure(newLp);
+                break;
+
+            default:
+                if (VDBG) { Log.d(TAG, "onLinkPropertiesChange()"); }
+                mCallback.onLinkPropertiesChange(newLp);
+                break;
+        }
+    }
+
     private ProvisioningChange setLinkProperties(LinkProperties newLp) {
         if (mIpReachabilityMonitor != null) {
             mIpReachabilityMonitor.updateLinkProperties(newLp);
         }
 
-        // TODO: Figure out whether and how to incorporate static configuration
-        // into the notion of provisioning.
         ProvisioningChange delta;
         synchronized (mLock) {
-            delta = LinkProperties.compareProvisioning(mLinkProperties, newLp);
+            delta = compareProvisioning(mLinkProperties, newLp);
             mLinkProperties = new LinkProperties(newLp);
         }
 
@@ -351,15 +470,45 @@
 
     private void handleIPv4Success(DhcpResults dhcpResults) {
         mDhcpResults = new DhcpResults(dhcpResults);
-        setLinkProperties(assembleLinkProperties());
-        mCallback.onIPv4ProvisioningSuccess(dhcpResults);
+        final LinkProperties newLp = assembleLinkProperties();
+        final ProvisioningChange delta = setLinkProperties(newLp);
+
+        if (VDBG) {
+            Log.d(TAG, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+        }
+        mCallback.onNewDhcpResults(dhcpResults);
+
+        dispatchCallback(delta, newLp);
     }
 
     private void handleIPv4Failure() {
+        // TODO: Figure out to de-dup this and the same code in DhcpClient.
         clearIPv4Address();
         mDhcpResults = null;
-        setLinkProperties(assembleLinkProperties());
-        mCallback.onIPv4ProvisioningFailure();
+        final LinkProperties newLp = assembleLinkProperties();
+        ProvisioningChange delta = setLinkProperties(newLp);
+        // If we've gotten here and we're still not provisioned treat that as
+        // a total loss of provisioning.
+        //
+        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+        // there was no usable IPv6 obtained before the DHCPv4 timeout.
+        //
+        // Regardless: GAME OVER.
+        //
+        // TODO: Make the DHCP client not time out and just continue in
+        // exponential backoff. Callers such as Wi-Fi which need a timeout
+        // should implement it themselves.
+        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        if (VDBG) { Log.d(TAG, "onNewDhcpResults(null)"); }
+        mCallback.onNewDhcpResults(null);
+
+        dispatchCallback(delta, newLp);
+        if (delta == ProvisioningChange.LOST_PROVISIONING) {
+            transitionTo(mStoppingState);
+        }
     }
 
     class StoppedState extends State {
@@ -391,13 +540,8 @@
                     break;
 
                 case DhcpStateMachine.CMD_ON_QUIT:
-                    // CMD_ON_QUIT is really more like "EVENT_ON_QUIT".
-                    // Shutting down DHCPv4 progresses simultaneously with
-                    // transitioning to StoppedState, so we can receive this
-                    // message after we've already transitioned here.
-                    //
-                    // TODO: Figure out if this is actually useful and if not
-                    // expunge it.
+                    // Everything is already stopped.
+                    Log.e(TAG, "Unexpected CMD_ON_QUIT (already stopped).");
                     break;
 
                 default:
@@ -407,6 +551,30 @@
         }
     }
 
+    class StoppingState extends State {
+        @Override
+        public void enter() {
+            if (mDhcpStateMachine == null) {
+                // There's no DHCPv4 for which to wait; proceed to stopped.
+                transitionTo(mStoppedState);
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case DhcpStateMachine.CMD_ON_QUIT:
+                    mDhcpStateMachine = null;
+                    transitionTo(mStoppedState);
+                    break;
+
+                default:
+                    deferMessage(msg);
+            }
+            return HANDLED;
+        }
+    }
+
     class StartedState extends State {
         @Override
         public void enter() {
@@ -439,7 +607,9 @@
                 if (applyStaticIpConfig()) {
                     handleIPv4Success(new DhcpResults(mStaticIpConfig));
                 } else {
-                    handleIPv4Failure();
+                    if (VDBG) { Log.d(TAG, "onProvisioningFailure()"); }
+                    mCallback.onProvisioningFailure(getLinkProperties());
+                    transitionTo(mStoppingState);
                 }
             } else {
                 // Start DHCPv4.
@@ -457,7 +627,6 @@
             if (mDhcpStateMachine != null) {
                 mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
                 mDhcpStateMachine.doQuit();
-                mDhcpStateMachine = null;
             }
 
             resetLinkProperties();
@@ -500,28 +669,15 @@
                         break;
                     }
                     final ProvisioningChange delta = setLinkProperties(newLp);
-
-                    // NOTE: The only receiver of these callbacks currently
-                    // treats all three of them identically, namely it calls
-                    // IpManager#getLinkProperties() and makes its own determination.
-                    switch (delta) {
-                        case GAINED_PROVISIONING:
-                            mCallback.onProvisioningSuccess(newLp);
-                            break;
-
-                        case LOST_PROVISIONING:
-                            mCallback.onProvisioningFailure(newLp);
-                            break;
-
-                        default:
-                            // TODO: Only notify on STILL_PROVISIONED?
-                            mCallback.onLinkPropertiesChange(newLp);
-                            break;
+                    dispatchCallback(delta, newLp);
+                    if (delta == ProvisioningChange.LOST_PROVISIONING) {
+                        transitionTo(mStoppedState);
                     }
                     break;
                 }
 
                 case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+                    if (VDBG) { Log.d(TAG, "onPreDhcpAction()"); }
                     mCallback.onPreDhcpAction();
                     break;
 
@@ -529,6 +685,7 @@
                     // Note that onPostDhcpAction() is likely to be
                     // asynchronous, and thus there is no guarantee that we
                     // will be able to observe any of its effects here.
+                    if (VDBG) { Log.d(TAG, "onPostDhcpAction()"); }
                     mCallback.onPostDhcpAction();
 
                     final DhcpResults dhcpResults = (DhcpResults) msg.obj;
@@ -546,11 +703,9 @@
                 }
 
                 case DhcpStateMachine.CMD_ON_QUIT:
-                    // CMD_ON_QUIT is really more like "EVENT_ON_QUIT".
-                    // Regardless, we ignore it.
-                    //
-                    // TODO: Figure out if this is actually useful and if not
-                    // expunge it.
+                    // DHCPv4 quit early for some reason.
+                    Log.e(TAG, "Unexpected CMD_ON_QUIT.");
+                    mDhcpStateMachine = null;
                     break;
 
                 default:
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 429839f..0d6c70b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -92,7 +92,33 @@
     int NO_SMS_TO_ACK = 48;                   /* ACK received when there is no SMS to ack */
     int NETWORK_ERR = 49;                     /* Received error from network */
     int REQUEST_RATE_LIMITED = 50;            /* Operation denied due to overly-frequent requests */
-
+    // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
+    // reveal particular replacement for Generic failure
+    int OEM_ERROR_1 = 501;
+    int OEM_ERROR_2 = 502;
+    int OEM_ERROR_3 = 503;
+    int OEM_ERROR_4 = 504;
+    int OEM_ERROR_5 = 505;
+    int OEM_ERROR_6 = 506;
+    int OEM_ERROR_7 = 507;
+    int OEM_ERROR_8 = 508;
+    int OEM_ERROR_9 = 509;
+    int OEM_ERROR_10 = 510;
+    int OEM_ERROR_11 = 511;
+    int OEM_ERROR_12 = 512;
+    int OEM_ERROR_13 = 513;
+    int OEM_ERROR_14 = 514;
+    int OEM_ERROR_15 = 515;
+    int OEM_ERROR_16 = 516;
+    int OEM_ERROR_17 = 517;
+    int OEM_ERROR_18 = 518;
+    int OEM_ERROR_19 = 519;
+    int OEM_ERROR_20 = 520;
+    int OEM_ERROR_21 = 521;
+    int OEM_ERROR_22 = 522;
+    int OEM_ERROR_23 = 523;
+    int OEM_ERROR_24 = 524;
+    int OEM_ERROR_25 = 525;
 
     /* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */
     int NETWORK_MODE_WCDMA_PREF     = 0; /* GSM/WCDMA (WCDMA preferred) */