Import Android SDK Platform P [4477446]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4477446 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4477446.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: If0559643d7c328e36aafca98f0c114641d33642c
diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java
index 2e4a5a4..22922e7 100644
--- a/com/android/systemui/OverviewProxyService.java
+++ b/com/android/systemui/OverviewProxyService.java
@@ -22,11 +22,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
-import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -36,12 +33,14 @@
 import android.util.Log;
 import android.view.SurfaceControl;
 
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.shared.system.GraphicBufferCompat;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -67,12 +66,12 @@
     private int mConnectionBackoffAttempts;
 
     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
-        public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
-                boolean useIdentityTransform, int rotation) {
+        public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer,
+                int maxLayer, boolean useIdentityTransform, int rotation) {
             long token = Binder.clearCallingIdentity();
             try {
-                return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
-                        useIdentityTransform, rotation);
+                return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width,
+                        height, minLayer, maxLayer, useIdentityTransform, rotation));
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/com/android/systemui/SystemUIFactory.java b/com/android/systemui/SystemUIFactory.java
index 0c067ff..526a8f4 100644
--- a/com/android/systemui/SystemUIFactory.java
+++ b/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@
 
     public ScrimController createScrimController(LightBarController lightBarController,
             ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-            LockscreenWallpaper lockscreenWallpaper,
-            Consumer<Boolean> scrimVisibleListener) {
+            LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+            DozeParameters dozeParameters) {
         return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                scrimVisibleListener);
+                scrimVisibleListener, dozeParameters);
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/com/android/systemui/doze/DozeHost.java b/com/android/systemui/doze/DozeHost.java
index 7db118d..2f607ee 100644
--- a/com/android/systemui/doze/DozeHost.java
+++ b/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@
     boolean isBlockingDoze();
 
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
-    void abortPulsing();
     void extendPulse();
 
     void setAnimateWakeup(boolean animateWakeup);
diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 03018f7..6ddc76b 100644
--- a/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -84,7 +84,7 @@
 
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+        return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
     }
 
     @Override
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index a35ba9f..c92acd0 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -60,7 +60,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.ViewGroup;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
@@ -129,7 +129,7 @@
  * false, this will override all other conditions for turning on the keyguard.
  *
  * Threading and synchronization:
- * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy},
+ * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants},
  * and runs on its thread.  The keyguard UI is created from that thread in the
  * constructor of this class.  The apis may be called from other threads, including the
  * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
@@ -766,8 +766,8 @@
 
     /**
      * Called to let us know the screen was turned off.
-     * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
-     *   {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+     * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or
+     *   {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
      */
     public void onStartedGoingToSleep(int why) {
         if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
@@ -797,8 +797,8 @@
                 }
             } else if (mShowing) {
                 mPendingReset = true;
-            } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
-                    || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+            } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
+                    || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) {
                 doKeyguardLaterLocked(timeout);
                 mLockLater = true;
             } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
@@ -1031,7 +1031,7 @@
     }
 
     /**
-     * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide
+     * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide
      * a way for external stuff to override normal keyguard behavior.  For instance
      * the phone app disables the keyguard when it receives incoming calls.
      */
@@ -1780,13 +1780,13 @@
                 int flags = 0;
                 if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
                         || mWakeAndUnlocking) {
-                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
                 }
                 if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
-                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
                 }
                 if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
-                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
                 }
 
                 mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
@@ -2028,12 +2028,9 @@
     }
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
-            ViewGroup container,
-            ScrimController scrimController,
-            FingerprintUnlockController fingerprintUnlockController) {
+            ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
-                scrimController, fingerprintUnlockController,
-                mDismissCallbackRegistry);
+                fingerprintUnlockController, mDismissCallbackRegistry);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/com/android/systemui/pip/phone/PipTouchHandler.java b/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0f..51175d1 100644
--- a/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -387,7 +387,9 @@
             }
             case MotionEvent.ACTION_HOVER_ENTER:
             case MotionEvent.ACTION_HOVER_MOVE: {
-                if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
+                if (mAccessibilityManager.isObservedEventType(
+                                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
+                        && !mSendingHoverAccessibilityEvents) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                     event.setImportantForAccessibility(true);
@@ -400,7 +402,9 @@
                 break;
             }
             case MotionEvent.ACTION_HOVER_EXIT: {
-                if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
+                if (mAccessibilityManager.isObservedEventType(
+                                AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
+                        && mSendingHoverAccessibilityEvents) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
                     event.setImportantForAccessibility(true);
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index eef43d2..a984680 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -625,9 +625,7 @@
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             if (getState() != STATE_NO_PIP) {
                 boolean hasPip = false;
 
@@ -662,9 +660,7 @@
         @Override
         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             StackInfo stackInfo = getPinnedStackInfo();
             if (stackInfo == null) {
                 Log.w(TAG, "Cannot find pinned stack");
@@ -690,9 +686,7 @@
         @Override
         public void onPinnedActivityRestartAttempt(boolean clearedTask) {
             if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
             movePipToFullscreen();
         }
@@ -700,9 +694,7 @@
         @Override
         public void onPinnedStackAnimationEnded() {
             if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             switch (getState()) {
                 case STATE_PIP_MENU:
                     showPipMenu();
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 3b1b2f9..663f206 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -23,14 +23,12 @@
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
@@ -156,7 +153,8 @@
                     // Launched from app is always the worst case (in terms of how many
                     // thumbnails/tasks visible)
                     launchState.launchedFromApp = true;
-                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
+                            -1 /* lastScrollPPresent */);
                     VisibilityReport visibilityReport =
                             mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
                                     stack.getTasks());
@@ -656,13 +654,6 @@
         // the resize mode already.
         if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
-            showRecents(
-                    false /* triggeredFromAltTab */,
-                    dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
-                    false /* animate */,
-                    true /* launchedWhileDockingTask*/,
-                    false /* fromHome */,
-                    DividerView.INVALID_RECENTS_GROW_TARGET);
         }
     }
 
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index d89bab7..2d3080b 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -21,12 +21,10 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
@@ -49,9 +47,6 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -63,7 +58,6 @@
 import android.util.Log;
 import android.util.MutableBoolean;
 import android.view.Display;
-import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
 import android.view.IWindowManager;
 import android.view.WindowManager;
@@ -74,16 +68,12 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
@@ -268,22 +258,6 @@
         return mIsSafeMode;
     }
 
-    /** Docks a task to the side of the screen and starts it. */
-    public boolean startTaskInDockedMode(int taskId, int createMode) {
-        if (mIam == null) return false;
-
-        try {
-            final ActivityOptions options = ActivityOptions.makeBasic();
-            options.setSplitScreenCreateMode(createMode);
-            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-            mIam.startActivityFromRecents(taskId, options.toBundle());
-            return true;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);
-        }
-        return false;
-    }
-
     /** Moves an already resumed task to the side of the screen to initiate split screen. */
     public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
             Rect initialBounds) {
@@ -397,7 +371,7 @@
         if (mIam == null) return false;
 
         try {
-            return mIam.isInLockTaskMode();
+            return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
         } catch (RemoteException e) {
             return false;
         }
@@ -540,16 +514,6 @@
         }
     }
 
-    public void overridePendingAppTransitionMultiThumbFuture(
-            IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
-            boolean scaleUp) {
-        try {
-            mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to override transition: " + e);
-        }
-    }
-
     /**
      * Updates the visibility of recents.
      */
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index 1440fc1..e3ed1aa 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui.recents.views;
 
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -33,11 +34,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
-import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -86,13 +87,15 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -608,16 +611,17 @@
             // rect to its final layout-space rect
             Utilities.setViewFrameFromTranslation(event.taskView);
 
-            // Dock the task and launch it
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
+            final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
+                    dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+            if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
+                    options)) {
                 final Runnable animStartedListener = () -> {
                     EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
-                    // Remove the task and don't bother relaying out, as all the tasks will be
-                    // relaid out when the stack changes on the multiwindow change event
+                    // Remove the task and don't bother relaying out, as all the tasks
+                    // will be relaid out when the stack changes on the multiwindow
+                    // change event
                     getStack().removeTask(event.task, null, true /* fromDockGesture */);
                 };
-
                 final Rect taskRect = getTaskRect(event.taskView);
                 AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
                         getHandler()) {
@@ -626,10 +630,8 @@
                         return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
                     }
                 };
-                ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
-                        RecentsTransition.wrapStartedListener(getHandler(), animStartedListener),
-                        true /* scaleUp */);
-
+                WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+                        future, animStartedListener, getHandler(), true /* scaleUp */);
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
                         event.task.getTopComponent().flattenToShortString());
             } else {
@@ -1032,11 +1034,9 @@
                 if (taskIndex > -1) {
                     taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
                 }
-                EventBus.getDefault().send(new LaunchTaskSucceededEvent(
-                        taskIndexFromFront));
+                EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
             } else {
-                Log.e(TAG, mContext.getString(R.string.recents_launch_error_message,
-                        task.title));
+                Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
 
                 // Dismiss the task if we fail to launch it
                 if (taskView != null) {
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 600da04..d9f79bb 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -431,7 +431,7 @@
      * in the stack.
      */
     public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
-            RecentsActivityLaunchState launchState) {
+            RecentsActivityLaunchState launchState, float lastScrollPPercent) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Clear the progress map
@@ -506,6 +506,8 @@
 
             if (launchState.launchedWithAltTab) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+            } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
+                mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
             } else if (Recents.getConfiguration().isLowRamDevice) {
                 mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
                         scrollToFront);
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 1197501..36c9095 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -209,6 +209,9 @@
     private int mLastHeight;
     private boolean mStackActionButtonVisible;
 
+    // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
+    private float mLastScrollPPercent;
+
     // We keep track of the task view focused by user interaction and draw a frame around it in the
     // grid layout.
     private TaskViewFocusFrame mTaskViewFocusFrame;
@@ -327,6 +330,7 @@
             mStackScroller.reset();
             mStableLayoutAlgorithm.reset();
             mLayoutAlgorithm.reset();
+            mLastScrollPPercent = -1;
         }
 
         // Since we always animate to the same place in (the initial state), always reset the stack
@@ -822,7 +826,7 @@
    public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
            RecentsActivityLaunchState launchState) {
         // Compute the min and max scroll values
-        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
+        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
 
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
@@ -1150,6 +1154,8 @@
         if (mTaskViewsClipDirty) {
             clipTaskViews();
         }
+        mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
+            mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
     }
 
     /**
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index 8e2a25c..4834bb1 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -45,6 +45,11 @@
  */
 public class RecentsTaskLoadPlan {
 
+    /** The set of conditions to preload tasks. */
+    public static class PreloadOptions {
+        public boolean loadTitles = true;
+    }
+
     /** The set of conditions to load tasks. */
     public static class Options {
         public int runningTaskId = -1;
@@ -80,7 +85,8 @@
      * Note: Do not lock, since this can be calling back to the loader, which separately also drives
      * this call (callers should synchronize on the loader before making this call).
      */
-    public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) {
+    public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
+            int currentUserId) {
         Resources res = mContext.getResources();
         ArrayList<Task> allTasks = new ArrayList<>();
         if (mRawTasks == null) {
@@ -110,9 +116,12 @@
             }
 
             // Load the title, icon, and color
-            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
-            String titleDescription = loader.getAndUpdateContentDescription(taskKey,
-                    t.taskDescription);
+            String title = opts.loadTitles
+                    ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
+                    : "";
+            String titleDescription = opts.loadTitles
+                    ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
+                    : "";
             Drawable icon = isStackTask
                     ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
                     : null;
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index 9a991cf..0f68026 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -155,7 +156,7 @@
             int currentUserId) {
         try {
             Trace.beginSection("preloadPlan");
-            plan.preloadPlan(this, runningTaskId, currentUserId);
+            plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
         } finally {
             Trace.endSection();
         }
diff --git a/com/android/systemui/shared/recents/utilities/AppTrace.java b/com/android/systemui/shared/recents/utilities/AppTrace.java
new file mode 100644
index 0000000..0241c59
--- /dev/null
+++ b/com/android/systemui/shared/recents/utilities/AppTrace.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.utilities;
+
+import static android.os.Trace.TRACE_TAG_APP;
+
+/**
+ * Helper class for internal trace functions.
+ */
+public class AppTrace {
+
+    /**
+     * Begins a new async trace section with the given {@param key} and {@param cookie}.
+     */
+    public static void start(String key, int cookie) {
+        android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, cookie);
+    }
+
+    /**
+     * Begins a new async trace section with the given {@param key}.
+     */
+    public static void start(String key) {
+        android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, 0);
+    }
+
+    /**
+     * Ends an existing async trace section with the given {@param key}.
+     */
+    public static void end(String key) {
+        android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, 0);
+    }
+
+    /**
+     * Ends an existing async trace section with the given {@param key} and {@param cookie}.
+     */
+    public static void end(String key, int cookie) {
+        android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, cookie);
+    }
+
+    /**
+     * Begins a new trace section with the given {@param key}. Can be nested.
+     */
+    public static void beginSection(String key) {
+        android.os.Trace.beginSection(key);
+    }
+
+    /**
+     * Ends an existing trace section started in the last {@link #beginSection(String)}.
+     */
+    public static void endSection() {
+        android.os.Trace.endSection();
+    }
+
+    /**
+     * Traces a counter value.
+     */
+    public static void count(String name, int count) {
+        android.os.Trace.traceCounter(TRACE_TAG_APP, name, count);
+    }
+}
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
index f6fab86..eb2d12e 100644
--- a/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -319,32 +319,39 @@
         mBackgroundExecutor.submit(new Runnable() {
             @Override
             public void run() {
+                boolean result = false;
                 try {
-                    ActivityManager.getService().startActivityFromRecents(taskKey.id,
-                            finalOptions == null ? null : finalOptions.toBundle());
-                    if (resultCallback != null) {
-                        resultCallbackHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                resultCallback.accept(true);
-                            }
-                        });
-                    }
+                    result = startActivityFromRecents(taskKey.id, finalOptions);
                 } catch (Exception e) {
-                    if (resultCallback != null) {
-                        resultCallbackHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                resultCallback.accept(false);
-                            }
-                        });
-                    }
+                    // Fall through
+                }
+                final boolean finalResult = result;
+                if (resultCallback != null) {
+                    resultCallbackHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            resultCallback.accept(finalResult);
+                        }
+                    });
                 }
             }
         });
     }
 
     /**
+     * Starts a task from Recents synchronously.
+     */
+    public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
+        try {
+            Bundle optsBundle = options == null ? null : options.toBundle();
+            ActivityManager.getService().startActivityFromRecents(taskId, optsBundle);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
      * Registers a task stack listener with the system.
      * This should be called on the main thread.
      */
diff --git a/com/android/systemui/shared/system/ActivityOptionsCompat.java b/com/android/systemui/shared/system/ActivityOptionsCompat.java
new file mode 100644
index 0000000..705a215
--- /dev/null
+++ b/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
+import android.app.ActivityOptions;
+
+/**
+ * Wrapper around internal ActivityOptions creation.
+ */
+public abstract class ActivityOptionsCompat {
+
+    /**
+     * @return ActivityOptions for starting a task in split screen.
+     */
+    public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        options.setSplitScreenCreateMode(dockTopLeft
+                ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+        return options;
+    }
+}
diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java
index cfd1f9a..0bd89a7 100644
--- a/com/android/systemui/shared/system/BackgroundExecutor.java
+++ b/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shared.system;
 
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -37,6 +38,13 @@
     }
 
     /**
+     * Runs the given {@param callable} on one of the background executor threads.
+     */
+    public <T> Future<T> submit(Callable<T> callable) {
+        return mExecutorService.submit(callable);
+    }
+
+    /**
      * Runs the given {@param runnable} on one of the background executor threads.
      */
     public Future<?> submit(Runnable runnable) {
diff --git a/com/android/systemui/shared/system/ChoreographerCompat.java b/com/android/systemui/shared/system/ChoreographerCompat.java
new file mode 100644
index 0000000..4d422bb
--- /dev/null
+++ b/com/android/systemui/shared/system/ChoreographerCompat.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.system;
+
+import static android.view.Choreographer.CALLBACK_INPUT;
+
+import android.view.Choreographer;
+
+/**
+ * Wraps the internal choreographer.
+ */
+public class ChoreographerCompat {
+
+    /**
+     * Posts an input callback to the choreographer.
+     */
+    public static void postInputFrame(Choreographer choreographer, Runnable runnable) {
+        choreographer.postCallback(CALLBACK_INPUT, runnable, null);
+    }
+}
diff --git a/com/android/systemui/shared/system/GraphicBufferCompat.java b/com/android/systemui/shared/system/GraphicBufferCompat.java
new file mode 100644
index 0000000..66b8fed
--- /dev/null
+++ b/com/android/systemui/shared/system/GraphicBufferCompat.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.system;
+
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Wraps the internal graphic buffer.
+ */
+public class GraphicBufferCompat implements Parcelable {
+
+    private GraphicBuffer mBuffer;
+
+    public GraphicBufferCompat(GraphicBuffer buffer) {
+        mBuffer = buffer;
+    }
+
+    public GraphicBufferCompat(Parcel in) {
+        mBuffer = GraphicBuffer.CREATOR.createFromParcel(in);
+    }
+
+    public Bitmap toBitmap() {
+        return mBuffer != null
+                ? Bitmap.createHardwareBitmap(mBuffer)
+                : null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mBuffer.writeToParcel(dest, flags);
+    }
+
+    public static final Parcelable.Creator<GraphicBufferCompat> CREATOR
+            = new Parcelable.Creator<GraphicBufferCompat>() {
+        public GraphicBufferCompat createFromParcel(Parcel in) {
+            return new GraphicBufferCompat(in);
+        }
+
+        public GraphicBufferCompat[] newArray(int size) {
+            return new GraphicBufferCompat[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java
index 1477558..225dbb4 100644
--- a/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -19,8 +19,15 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.WindowManagerGlobal;
 
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
 public class WindowManagerWrapper {
 
     private static final String TAG = "WindowManagerWrapper";
@@ -38,8 +45,24 @@
         try {
             WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
                     outStableInsets);
-        } catch (Exception e) {
-            e.printStackTrace();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get stable insets", e);
+        }
+    }
+
+    /**
+     * Overrides a pending app transition.
+     */
+    public void overridePendingAppTransitionMultiThumbFuture(
+            AppTransitionAnimationSpecsFuture animationSpecFuture,
+            Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp) {
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(),
+                            RecentsTransition.wrapStartedListener(animStartedCallbackHandler,
+                                    animStartedCallback), scaleUp);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e);
         }
     }
 }
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 1cda301..da79884 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -20,6 +20,8 @@
 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.os.UserHandle.USER_CURRENT;
 
+import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE;
+
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.os.RemoteException;
@@ -36,6 +38,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.DividerView;
+import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 
 import java.util.List;
 
@@ -89,20 +92,11 @@
         try {
             int dockSide = mWindowManagerService.getDockedStackSide();
             if (dockSide == WindowManager.DOCKED_INVALID) {
-                // If there is no window docked, we dock the top-most window.
+                // Split the screen
                 Recents recents = getComponent(Recents.class);
-                int dockMode = (shortcutCode == SC_DOCK_LEFT)
+                recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT)
                         ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-                List<ActivityManager.RecentTaskInfo> taskList =
-                        ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
-                recents.showRecentApps(
-                        false /* triggeredFromAltTab */,
-                        false /* fromHome */);
-                if (!taskList.isEmpty()) {
-                    SystemServicesProxy.getInstance(mContext).startTaskInDockedMode(
-                            taskList.get(0).id, dockMode);
-                }
+                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
             } else {
                 // If there is already a docked window, we respond by resizing the docking pane.
                 DividerView dividerView = getComponent(Divider.class).getView();
diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java
index 84b7015..ff0357a 100644
--- a/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -695,6 +695,11 @@
                 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
     }
 
+    protected void updateBackgroundClipping() {
+        mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+        mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+    }
+
     protected boolean shouldHideBackground() {
         return mDark;
     }
@@ -901,12 +906,45 @@
         contentView.setAlpha(contentAlpha);
     }
 
+    @Override
+    protected void applyRoundness() {
+        super.applyRoundness();
+        applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+                getCurrentBackgroundRadiusBottom());
+    }
+
+    protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+        mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+        mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+    }
+
+    @Override
+    protected void setBackgroundTop(int backgroundTop) {
+        mBackgroundDimmed.setBackgroundTop(backgroundTop);
+        mBackgroundNormal.setBackgroundTop(backgroundTop);
+    }
+
     protected abstract View getContentView();
 
     public int calculateBgColor() {
         return calculateBgColor(true /* withTint */, true /* withOverRide */);
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        super.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+    }
+
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+            return true;
+        }
+        return super.childNeedsClipping(child);
+    }
+
     /**
      * @param withTint should a possible tint be factored in?
      * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/com/android/systemui/statusbar/CommandQueue.java b/com/android/systemui/statusbar/CommandQueue.java
index 6349275..8e1b104 100644
--- a/com/android/systemui/statusbar/CommandQueue.java
+++ b/com/android/systemui/statusbar/CommandQueue.java
@@ -82,6 +82,7 @@
     private static final int MSG_TOGGLE_PANEL                  = 35 << MSG_SHIFT;
     private static final int MSG_SHOW_SHUTDOWN_UI              = 36 << MSG_SHIFT;
     private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR  = 37 << MSG_SHIFT;
+    private static final int MSG_ROTATION_PROPOSAL             = 38 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -142,6 +143,8 @@
         default void handleSystemKey(int arg1) { }
         default void handleShowGlobalActionsMenu() { }
         default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+        default void onRotationProposal(int rotation) { }
     }
 
     @VisibleForTesting
@@ -458,6 +461,15 @@
         }
     }
 
+    @Override
+    public void onProposedRotationChanged(int rotation) {
+        synchronized (mLock) {
+            mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+            mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0,
+                    null).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -654,6 +666,11 @@
                         mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
                     }
                     break;
+                case MSG_ROTATION_PROPOSAL:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).onRotationProposal(msg.arg1);
+                    }
+                    break;
             }
         }
     }
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ff950e..23d9cae 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -102,7 +102,9 @@
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mNotificationMinHeightLegacy;
+    private int mNotificationMinHeightBeforeP;
     private int mMaxHeadsUpHeightLegacy;
+    private int mMaxHeadsUpHeightBeforeP;
     private int mMaxHeadsUpHeight;
     private int mMaxHeadsUpHeightIncreased;
     private int mNotificationMinHeight;
@@ -435,9 +437,10 @@
         boolean customView = layout.getContractedChild().getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+        boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
         int minHeight;
-        if (customView && beforeN && !mIsSummaryWithChildren) {
-            minHeight = mNotificationMinHeightLegacy;
+        if (customView && beforeP && !mIsSummaryWithChildren) {
+            minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
             minHeight = mNotificationMinHeightLarge;
         } else {
@@ -447,8 +450,8 @@
                 layout.getHeadsUpChild().getId()
                         != com.android.internal.R.id.status_bar_latest_event_content;
         int headsUpheight;
-        if (headsUpCustom && beforeN) {
-            headsUpheight = mMaxHeadsUpHeightLegacy;
+        if (headsUpCustom && beforeP) {
+            headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
         } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
             headsUpheight = mMaxHeadsUpHeightIncreased;
         } else {
@@ -535,6 +538,7 @@
         }
         onChildrenCountChanged();
         row.setIsChildInGroup(false, null);
+        row.setBottomRoundness(0.0f, false /* animate */);
     }
 
     @Override
@@ -563,6 +567,7 @@
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateIconVisibilities();
+        updateBackgroundClipping();
     }
 
     @Override
@@ -916,6 +921,7 @@
             addView(mMenuRow.getMenuView(), menuIndex);
         }
         for (NotificationContentView l : mLayouts) {
+            l.initView();
             l.reInflateViews();
         }
         mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1025,6 +1031,7 @@
         mKeepInParent = keepInParent;
     }
 
+    @Override
     public boolean isRemoved() {
         return mRemoved;
     }
@@ -1264,6 +1271,8 @@
     private void initDimens() {
         mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_legacy);
+        mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height_before_p);
         mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height);
         mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
@@ -1274,6 +1283,8 @@
                 R.dimen.notification_ambient_height);
         mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
+        mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_heads_up_height_before_p);
         mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height);
         mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
@@ -1752,6 +1763,7 @@
         mPrivateLayout.updateExpandButtons(isExpandable());
         updateChildrenHeaderAppearance();
         updateChildrenVisibility();
+        applyChildrenRoundness();
     }
 
     public void updateChildrenHeaderAppearance() {
@@ -2332,6 +2344,56 @@
         }
     }
 
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationContentView) {
+            NotificationContentView contentView = (NotificationContentView) child;
+            if (isClippingNeeded()) {
+                return true;
+            } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+                return true;
+            }
+        } else if (child == mChildrenContainer) {
+            if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging())
+                    && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) {
+                return true;
+            }
+        } else if (child instanceof NotificationGuts) {
+            return !hasNoRoundingAndNoPadding();
+        }
+        return super.childNeedsClipping(child);
+    }
+
+    @Override
+    protected void applyRoundness() {
+        super.applyRoundness();
+        applyChildrenRoundness();
+    }
+
+    private void applyChildrenRoundness() {
+        if (mIsSummaryWithChildren) {
+            mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+        }
+    }
+
+    @Override
+    public Path getCustomClipPath(View child) {
+        if (child instanceof NotificationGuts) {
+            return getClipPath(true, /* ignoreTranslation */
+                    false /* clipRoundedToBottom */);
+        }
+        if (child instanceof NotificationChildrenContainer) {
+            return getClipPath(false, /* ignoreTranslation */
+                    true /* clipRoundedToBottom */);
+        }
+        return super.getCustomClipPath(child);
+    }
+
+    private boolean hasNoRoundingAndNoPadding() {
+        return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+                && getCurrentTopRoundness() == 0.0f;
+    }
+
     public boolean isShowingAmbient() {
         return mShowAmbient;
     }
@@ -2344,6 +2406,20 @@
         }
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        if (mIsSummaryWithChildren) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mChildrenContainer.getNotificationChildren();
+            int size = notificationChildren.size();
+            for (int i = 0; i < size; i++) {
+                ExpandableNotificationRow row = notificationChildren.get(i);
+                row.setCurrentSidePaddings(currentSidePaddings);
+            }
+        }
+        super.setCurrentSidePaddings(currentSidePaddings);
+    }
+
     public static class NotificationViewState extends ExpandableViewState {
 
         private final StackScrollState mOverallState;
diff --git a/com/android/systemui/statusbar/ExpandableOutlineView.java b/com/android/systemui/statusbar/ExpandableOutlineView.java
index 2556890..b3d6e32 100644
--- a/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,23 +18,58 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Outline;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
  * Like {@link ExpandableView}, but setting an outline for the height and clipping.
  */
 public abstract class ExpandableOutlineView extends ExpandableView {
 
+    private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+            "topRoundness",
+            ExpandableOutlineView::setTopRoundnessInternal,
+            ExpandableOutlineView::getCurrentTopRoundness,
+            R.id.top_roundess_animator_tag,
+            R.id.top_roundess_animator_end_tag,
+            R.id.top_roundess_animator_start_tag);
+    private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+            "bottomRoundness",
+            ExpandableOutlineView::setBottomRoundnessInternal,
+            ExpandableOutlineView::getCurrentBottomRoundness,
+            R.id.bottom_roundess_animator_tag,
+            R.id.bottom_roundess_animator_end_tag,
+            R.id.bottom_roundess_animator_start_tag);
+    private static final AnimationProperties ROUNDNESS_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    private static final Path EMPTY_PATH = new Path();
+
     private final Rect mOutlineRect = new Rect();
     private boolean mCustomOutline;
     private float mOutlineAlpha = -1f;
     private float mOutlineRadius;
+    private boolean mAlwaysRoundBothCorners;
+    private Path mTmpPath = new Path();
+    private Path mTmpPath2 = new Path();
+    private float mCurrentBottomRoundness;
+    private float mCurrentTopRoundness;
+    private float mBottomRoundness;
+    private float mTopRoundness;
+    private int mBackgroundTop;
+    protected int mCurrentSidePaddings;
 
     /**
      * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +80,248 @@
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
         public void getOutline(View view, Outline outline) {
-            int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
-            if (!mCustomOutline) {
-                outline.setRoundRect(translation,
-                        mClipTopAmount,
-                        getWidth() + translation,
-                        Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
-                        mOutlineRadius);
-            } else {
-                outline.setRoundRect(mOutlineRect, mOutlineRadius);
+            Path clipPath = getClipPath();
+            if (clipPath != null && clipPath.isConvex()) {
+                // The path might not be convex in border cases where the view is small and clipped
+                outline.setConvexPath(clipPath);
             }
             outline.setAlpha(mOutlineAlpha);
         }
     };
 
+    private Path getClipPath() {
+        return getClipPath(false, /* ignoreTranslation */
+                false /* clipRoundedToBottom */);
+    }
+
+    protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+        int left;
+        int top;
+        int right;
+        int bottom;
+        int height;
+        Path intersectPath = null;
+        if (!mCustomOutline) {
+            int translation = mShouldTranslateContents && !ignoreTranslation
+                    ? (int) getTranslation() : 0;
+            left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+            top = mClipTopAmount + mBackgroundTop;
+            right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+            bottom = Math.max(getActualHeight(), top);
+            int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+            if (bottom != intersectBottom) {
+                if (clipRoundedToBottom) {
+                    bottom = intersectBottom;
+                } else {
+                    getRoundedRectPath(left, top, right,
+                            intersectBottom, 0.0f,
+                            0.0f, mTmpPath2);
+                    intersectPath = mTmpPath2;
+                }
+            }
+        } else {
+            left = mOutlineRect.left;
+            top = mOutlineRect.top;
+            right = mOutlineRect.right;
+            bottom = mOutlineRect.bottom;
+            left = Math.max(mCurrentSidePaddings, left);
+            right = Math.min(getWidth() - mCurrentSidePaddings, right);
+        }
+        height = bottom - top;
+        if (height == 0) {
+            return EMPTY_PATH;
+        }
+        float topRoundness = mAlwaysRoundBothCorners
+                ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+        float bottomRoundness = mAlwaysRoundBothCorners
+                ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+        if (topRoundness + bottomRoundness > height) {
+            float overShoot = topRoundness + bottomRoundness - height;
+            topRoundness -= overShoot * mCurrentTopRoundness
+                    / (mCurrentTopRoundness + mCurrentBottomRoundness);
+            bottomRoundness -= overShoot * mCurrentBottomRoundness
+                    / (mCurrentTopRoundness + mCurrentBottomRoundness);
+        }
+        getRoundedRectPath(left, top, right, bottom, topRoundness,
+                bottomRoundness, mTmpPath);
+        Path roundedRectPath = mTmpPath;
+        if (intersectPath != null) {
+            roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+        }
+        return roundedRectPath;
+    }
+
+    protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+            float bottomRoundness) {
+        getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
+                mTmpPath);
+        return mTmpPath;
+    }
+
+    private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+            float bottomRoundness, Path outPath) {
+        outPath.reset();
+        int width = right - left;
+        float topRoundnessX = topRoundness;
+        float bottomRoundnessX = bottomRoundness;
+        topRoundnessX = Math.min(width / 2, topRoundnessX);
+        bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+        if (topRoundness > 0.0f) {
+            outPath.moveTo(left, top + topRoundness);
+            outPath.quadTo(left, top, left + topRoundnessX, top);
+            outPath.lineTo(right - topRoundnessX, top);
+            outPath.quadTo(right, top, right, top + topRoundness);
+        } else {
+            outPath.moveTo(left, top);
+            outPath.lineTo(right, top);
+        }
+        if (bottomRoundness > 0.0f) {
+            outPath.lineTo(right, bottom - bottomRoundness);
+            outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+            outPath.lineTo(left + bottomRoundnessX, bottom);
+            outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+        } else {
+            outPath.lineTo(right, bottom);
+            outPath.lineTo(left, bottom);
+        }
+        outPath.close();
+    }
+
     public ExpandableOutlineView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOutlineProvider(mProvider);
         initDimens();
     }
 
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        canvas.save();
+        if (childNeedsClipping(child)) {
+            Path clipPath = getCustomClipPath(child);
+            if (clipPath == null) {
+                clipPath = getClipPath();
+            }
+            if (clipPath != null) {
+                canvas.clipPath(clipPath);
+            }
+        }
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        canvas.restore();
+        return result;
+    }
+
+    protected boolean childNeedsClipping(View child) {
+        return false;
+    }
+
+    protected boolean isClippingNeeded() {
+        return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+    }
+
     private void initDimens() {
         Resources res = getResources();
         mShouldTranslateContents =
                 res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
         mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
-        setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+        mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+        if (!mAlwaysRoundBothCorners) {
+            mOutlineRadius = res.getDimensionPixelSize(
+                    Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+        }
+        setClipToOutline(mAlwaysRoundBothCorners);
+    }
+
+    public void setTopRoundness(float topRoundness, boolean animate) {
+        if (mTopRoundness != topRoundness) {
+            mTopRoundness = topRoundness;
+            PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+                    ROUNDNESS_PROPERTIES, animate);
+        }
+    }
+
+    protected void applyRoundness() {
+        invalidateOutline();
+        invalidate();
+    }
+
+    public float getCurrentBackgroundRadiusTop() {
+        return mCurrentTopRoundness * mOutlineRadius;
+    }
+
+    public float getCurrentTopRoundness() {
+        return mCurrentTopRoundness;
+    }
+
+    public float getCurrentBottomRoundness() {
+        return mCurrentBottomRoundness;
+    }
+
+    protected float getCurrentBackgroundRadiusBottom() {
+        return mCurrentBottomRoundness * mOutlineRadius;
+    }
+
+    public void setBottomRoundness(float bottomRoundness, boolean animate) {
+        if (mBottomRoundness != bottomRoundness) {
+            mBottomRoundness = bottomRoundness;
+            PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+                    ROUNDNESS_PROPERTIES, animate);
+        }
+    }
+
+    protected void setBackgroundTop(int backgroundTop) {
+        if (mBackgroundTop != backgroundTop) {
+            mBackgroundTop = backgroundTop;
+            invalidateOutline();
+        }
+    }
+
+    private void setTopRoundnessInternal(float topRoundness) {
+        mCurrentTopRoundness = topRoundness;
+        applyRoundness();
+    }
+
+    private void setBottomRoundnessInternal(float bottomRoundness) {
+        mCurrentBottomRoundness = bottomRoundness;
+        applyRoundness();
     }
 
     public void onDensityOrFontScaleChanged() {
         initDimens();
-        invalidateOutline();
+        applyRoundness();
     }
 
     @Override
     public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        int previousHeight = getActualHeight();
         super.setActualHeight(actualHeight, notifyListeners);
-        invalidateOutline();
+        if (previousHeight != actualHeight) {
+            applyRoundness();
+        }
     }
 
     @Override
     public void setClipTopAmount(int clipTopAmount) {
+        int previousAmount = getClipTopAmount();
         super.setClipTopAmount(clipTopAmount);
-        invalidateOutline();
+        if (previousAmount != clipTopAmount) {
+            applyRoundness();
+        }
     }
 
     @Override
     public void setClipBottomAmount(int clipBottomAmount) {
+        int previousAmount = getClipBottomAmount();
         super.setClipBottomAmount(clipBottomAmount);
-        invalidateOutline();
+        if (previousAmount != clipBottomAmount) {
+            applyRoundness();
+        }
     }
 
     protected void setOutlineAlpha(float alpha) {
         if (alpha != mOutlineAlpha) {
             mOutlineAlpha = alpha;
-            invalidateOutline();
+            applyRoundness();
         }
     }
 
@@ -113,8 +335,7 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            setClipToOutline(false);
-            invalidateOutline();
+            applyRoundness();
         }
     }
 
@@ -151,15 +372,22 @@
 
     protected void setOutlineRect(float left, float top, float right, float bottom) {
         mCustomOutline = true;
-        setClipToOutline(true);
 
         mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
 
         // Outlines need to be at least 1 dp
         mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
         mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-
-        invalidateOutline();
+        applyRoundness();
     }
 
+    public Path getCustomClipPath(View child) {
+        return null;
+    }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidateOutline();
+        invalidate();
+    }
 }
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index aac9af8..18b9860 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -202,6 +202,10 @@
         return mDark;
     }
 
+    public boolean isRemoved() {
+        return false;
+    }
+
     /**
      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
diff --git a/com/android/systemui/statusbar/NotificationBackgroundView.java b/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc..68cf51c 100644
--- a/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,57 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
-import android.graphics.ColorFilter;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.systemui.R;
+
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
  */
 public class NotificationBackgroundView extends View {
 
+    private final boolean mDontModifyCorners;
     private Drawable mBackground;
     private int mClipTopAmount;
     private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
+    private float[] mCornerRadii = new float[8];
+    private int mCurrentSidePaddings;
+    private boolean mBottomIsRounded;
+    private int mBackgroundTop;
+    private boolean mBottomAmountClips = true;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mDontModifyCorners = getResources().getBoolean(
+                R.bool.config_clipNotificationsToOutline);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
-        draw(canvas, mBackground);
+        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+            canvas.save();
+            canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+            draw(canvas, mBackground);
+            canvas.restore();
+        }
     }
 
     private void draw(Canvas canvas, Drawable drawable) {
-        int bottom = mActualHeight - mClipBottomAmount;
-        if (drawable != null && bottom > mClipTopAmount) {
-            drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+        if (drawable != null) {
+            int bottom = mActualHeight;
+            if (mBottomIsRounded && mBottomAmountClips) {
+                bottom -= mClipBottomAmount;
+            }
+            drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+                    getWidth() - mCurrentSidePaddings, bottom);
             drawable.draw(canvas);
         }
     }
@@ -87,6 +107,7 @@
             unscheduleDrawable(mBackground);
         }
         mBackground = background;
+        mBackground.mutate();
         if (mBackground != null) {
             mBackground.setCallback(this);
             setTint(mTintColor);
@@ -94,6 +115,7 @@
         if (mBackground instanceof RippleDrawable) {
             ((RippleDrawable) mBackground).setForceSoftware(true);
         }
+        updateBackgroundRadii();
         invalidate();
     }
 
@@ -152,4 +174,45 @@
     public void setDrawableAlpha(int drawableAlpha) {
         mBackground.setAlpha(drawableAlpha);
     }
+
+    public void setRoundness(float topRoundness, float bottomRoundNess) {
+        mBottomIsRounded = bottomRoundNess != 0.0f;
+        mCornerRadii[0] = topRoundness;
+        mCornerRadii[1] = topRoundness;
+        mCornerRadii[2] = topRoundness;
+        mCornerRadii[3] = topRoundness;
+        mCornerRadii[4] = bottomRoundNess;
+        mCornerRadii[5] = bottomRoundNess;
+        mCornerRadii[6] = bottomRoundNess;
+        mCornerRadii[7] = bottomRoundNess;
+        updateBackgroundRadii();
+    }
+
+    public void setBottomAmountClips(boolean clips) {
+        if (clips != mBottomAmountClips) {
+            mBottomAmountClips = clips;
+            invalidate();
+        }
+    }
+
+    private void updateBackgroundRadii() {
+        if (mDontModifyCorners) {
+            return;
+        }
+        if (mBackground instanceof LayerDrawable) {
+            GradientDrawable gradientDrawable =
+                    (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+            gradientDrawable.setCornerRadii(mCornerRadii);
+        }
+    }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidate();
+    }
+
+    public void setBackgroundTop(int backgroundTop) {
+        mBackgroundTop = backgroundTop;
+        invalidate();
+    }
 }
diff --git a/com/android/systemui/statusbar/NotificationContentView.java b/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c8..39c2131 100644
--- a/com/android/systemui/statusbar/NotificationContentView.java
+++ b/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,8 +35,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -49,6 +50,7 @@
  */
 public class NotificationContentView extends FrameLayout {
 
+    private static final String TAG = "NotificationContentView";
     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     public static final int VISIBLE_TYPE_EXPANDED = 1;
     public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +60,9 @@
     public static final int UNDEFINED = -1;
 
     private final Rect mClipBounds = new Rect();
-    private final int mMinContractedHeight;
-    private final int mNotificationContentMarginEnd;
 
+    private int mMinContractedHeight;
+    private int mNotificationContentMarginEnd;
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
@@ -134,15 +136,22 @@
     private int mClipBottomAmount;
     private boolean mIsLowPriority;
     private boolean mIsContentExpandable;
+    private int mCustomViewSidePaddings;
 
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext(), this);
+        initView();
+    }
+
+    public void initView() {
         mMinContractedHeight = getResources().getDimensionPixelSize(
                 R.dimen.min_notification_layout_height);
         mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_content_margin_end);
+        mCustomViewSidePaddings = getResources().getDimensionPixelSize(
+                R.dimen.notification_content_custom_view_side_padding);
     }
 
     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -178,7 +187,7 @@
                     : MeasureSpec.makeMeasureSpec(size, useExactly
                             ? MeasureSpec.EXACTLY
                             : MeasureSpec.AT_MOST);
-            mExpandedChild.measure(widthMeasureSpec, spec);
+            measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
         }
         if (mContractedChild != null) {
@@ -196,22 +205,22 @@
             } else {
                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
             }
-            mContractedChild.measure(widthMeasureSpec, heightSpec);
+            measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             int measuredHeight = mContractedChild.getMeasuredHeight();
             if (measuredHeight < mMinContractedHeight) {
                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
-                mContractedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
             if (updateContractedHeaderWidth()) {
-                mContractedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
             if (mExpandedChild != null
                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
                         MeasureSpec.EXACTLY);
-                mExpandedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
         }
         if (mHeadsUpChild != null) {
@@ -223,9 +232,9 @@
                 size = Math.min(size, layoutParams.height);
                 useExactly = true;
             }
-            mHeadsUpChild.measure(widthMeasureSpec,
+            measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
-                            : MeasureSpec.AT_MOST));
+                            : MeasureSpec.AT_MOST), 0);
             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
         }
         if (mSingleLineView != null) {
@@ -382,6 +391,38 @@
         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+        updateMargins(child);
+    }
+
+    private void updateMargins(View child) {
+        if (child == null) {
+            return;
+        }
+        NotificationViewWrapper wrapper = getWrapperForView(child);
+        boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper;
+        boolean needsMargins = isCustomView &&
+                child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P;
+        int padding = needsMargins ? mCustomViewSidePaddings : 0;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+        layoutParams.setMarginStart(padding);
+        layoutParams.setMarginEnd(padding);
+        child.setLayoutParams(layoutParams);
+    }
+
+    private NotificationViewWrapper getWrapperForView(View child) {
+        if (child == mContractedChild) {
+            return mContractedWrapper;
+        }
+        if (child == mExpandedChild) {
+            return mExpandedWrapper;
+        }
+        if (child == mHeadsUpChild) {
+            return mHeadsUpWrapper;
+        }
+        if (child == mAmbientChild) {
+            return mAmbientWrapper;
+        }
+        return null;
     }
 
     public void setExpandedChild(View child) {
@@ -415,6 +456,7 @@
         mExpandedChild = child;
         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+        updateMargins(child);
     }
 
     public void setHeadsUpChild(View child) {
@@ -448,6 +490,7 @@
         mHeadsUpChild = child;
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+        updateMargins(child);
     }
 
     public void setAmbientChild(View child) {
@@ -643,6 +686,13 @@
         int endHeight = getViewForVisibleType(mVisibleType).getHeight();
         int progress = Math.abs(mContentHeight - startHeight);
         int totalDistance = Math.abs(endHeight - startHeight);
+        if (totalDistance == 0) {
+            Log.wtf(TAG, "the total transformation distance is 0"
+                    + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+                    + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+                    + "\n mContentHeight: " + mContentHeight);
+            return 1.0f;
+        }
         float amount = (float) progress / (float) totalDistance;
         return Math.min(1.0f, amount);
     }
@@ -1459,4 +1509,20 @@
         }
         return false;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+        if (mUserExpanding) {
+             needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+        }
+        return needsPaddings;
+    }
+
+    private boolean shouldClipToSidePaddings(int visibleType) {
+        NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+        if (visibleWrapper == null) {
+            return false;
+        }
+        return visibleWrapper.shouldClipToSidePaddings();
+    }
 }
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
index b585bdf..f451fda 100644
--- a/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -37,6 +37,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
@@ -75,17 +76,20 @@
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
     private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private final OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
 
     public NotificationGutsManager(
             NotificationPresenter presenter,
             NotificationStackScrollLayout stackScroller,
             NotificationInfo.CheckSaveListener checkSaveListener,
-            Context context) {
+            Context context,
+            OnSettingsClickListener onSettingsClickListener) {
         mPresenter = presenter;
         mStackScroller = stackScroller;
         mCheckSaveListener = checkSaveListener;
         mContext = context;
+        mOnSettingsClickListener = onSettingsClickListener;
         Resources res = context.getResources();
 
         mNonBlockablePkgs = new HashSet<>();
@@ -189,6 +193,7 @@
                 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
                     guts.resetFalsingCheck();
+                    mOnSettingsClickListener.onClick(sbn.getKey());
                     startAppNotificationSettingsActivity(pkg, appUid, channel);
                 };
             }
@@ -352,4 +357,8 @@
         pw.print("mKeyToRemoveOnGutsClosed: ");
         pw.println(mKeyToRemoveOnGutsClosed);
     }
+
+    public interface OnSettingsClickListener {
+        void onClick(String key);
+    }
 }
diff --git a/com/android/systemui/statusbar/NotificationMenuRow.java b/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b07..b2604fe 100644
--- a/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@
     private float mHorizSpaceForIcon = -1;
     private int mVertSpaceForIcons = -1;
     private int mIconPadding = -1;
+    private int mSidePadding;
 
     private float mAlpha = 0f;
     private float mPrevX;
@@ -175,6 +176,7 @@
         final Resources res = mContext.getResources();
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
         mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
         mMenuItems.clear();
         // Construct the menu items based on the notification
@@ -496,8 +498,8 @@
         final int count = mMenuContainer.getChildCount();
         for (int i = 0; i < count; i++) {
             final View v = mMenuContainer.getChildAt(i);
-            final float left = i * mHorizSpaceForIcon;
-            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+            final float left = mSidePadding + i * mHorizSpaceForIcon;
+            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
             v.setX(showOnLeft ? left : right);
         }
         mOnLeft = showOnLeft;
diff --git a/com/android/systemui/statusbar/NotificationShelf.java b/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde..b7a00eb 100644
--- a/com/android/systemui/statusbar/NotificationShelf.java
+++ b/com/android/systemui/statusbar/NotificationShelf.java
@@ -85,6 +85,7 @@
     private boolean mVibrationOnAnimation;
     private boolean mUserTouchingScreen;
     private boolean mTouchActive;
+    private float mFirstElementRoundness;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -107,6 +108,7 @@
         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                 NotificationPanelView.DOZE_ANIMATION_DURATION);
         mShelfState = new ShelfState();
+        setBottomRoundness(1.0f, false /* animate */);
         initDimens();
     }
 
@@ -252,6 +254,8 @@
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
+        int backgroundTop = 0;
+        float firstElementRoundness = 0.0f;
         while (notificationIndex < mHostLayout.getChildCount()) {
             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
             notificationIndex++;
@@ -302,9 +306,20 @@
             if (notGoneIndex != 0 || !aboveShelf) {
                 row.setAboveShelf(false);
             }
+            if (notGoneIndex == 0) {
+                StatusBarIconView icon = row.getEntry().expandedIcon;
+                NotificationIconContainer.IconState iconState = getIconState(icon);
+                if (iconState.clampedAppearAmount == 1.0f) {
+                    // only if the first icon is fully in the shelf we want to clip to it!
+                    backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+                    firstElementRoundness = row.getCurrentTopRoundness();
+                }
+            }
             notGoneIndex++;
             previousColor = ownColorUntinted;
         }
+        setBackgroundTop(backgroundTop);
+        setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
@@ -325,6 +340,13 @@
         }
     }
 
+    private void setFirstElementRoundness(float firstElementRoundness) {
+        if (mFirstElementRoundness != firstElementRoundness) {
+            mFirstElementRoundness = firstElementRoundness;
+            setTopRoundness(firstElementRoundness, false /* animate */);
+        }
+    }
+
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/com/android/systemui/statusbar/RemoteInputController.java b/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c..ff6c775 100644
--- a/com/android/systemui/statusbar/RemoteInputController.java
+++ b/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,7 +19,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
 import android.util.ArrayMap;
@@ -38,11 +37,11 @@
             = new ArrayList<>();
     private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
     private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
-    private final HeadsUpManager mHeadsUpManager;
+    private final Delegate mDelegate;
 
-    public RemoteInputController(HeadsUpManager headsUpManager) {
+    public RemoteInputController(Delegate delegate) {
         addCallback(Dependency.get(StatusBarWindowManager.class));
-        mHeadsUpManager = headsUpManager;
+        mDelegate = delegate;
     }
 
     /**
@@ -114,7 +113,7 @@
     }
 
     private void apply(NotificationData.Entry entry) {
-        mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+        mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
         boolean remoteInputActive = isRemoteInputActive();
         int N = mCallbacks.size();
         for (int i = 0; i < N; i++) {
@@ -204,9 +203,35 @@
         }
     }
 
+    public void requestDisallowLongPressAndDismiss() {
+        mDelegate.requestDisallowLongPressAndDismiss();
+    }
+
+    public void lockScrollTo(NotificationData.Entry entry) {
+        mDelegate.lockScrollTo(entry);
+    }
+
     public interface Callback {
         default void onRemoteInputActive(boolean active) {}
 
         default void onRemoteInputSent(NotificationData.Entry entry) {}
     }
+
+    public interface Delegate {
+        /**
+         * Activate remote input if necessary.
+         */
+        void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+       /**
+        * Request that the view does not dismiss nor perform long press for the current touch.
+        */
+       void requestDisallowLongPressAndDismiss();
+
+      /**
+       * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+       * the user scrolls, or {@param v} loses focus or is detached.
+       */
+       void lockScrollTo(NotificationData.Entry entry);
+    }
 }
diff --git a/com/android/systemui/statusbar/ScrimView.java b/com/android/systemui/statusbar/ScrimView.java
index a53e348..8830352 100644
--- a/com/android/systemui/statusbar/ScrimView.java
+++ b/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
@@ -50,6 +51,7 @@
 public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
     private static final String TAG = "ScrimView";
     private final ColorExtractor.GradientColors mColors;
+    private int mDensity;
     private boolean mDrawAsSrc;
     private float mViewAlpha = 1.0f;
     private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@
         }
     };
     private Runnable mChangeRunnable;
+    private int mCornerRadius;
 
     public ScrimView(Context context) {
         this(context, null);
@@ -93,6 +96,24 @@
         mColors = new ColorExtractor.GradientColors();
         updateScreenSize();
         updateColorWithTint(false);
+        initView();
+        final Configuration currentConfig = mContext.getResources().getConfiguration();
+        mDensity = currentConfig.densityDpi;
+    }
+
+    private void initView() {
+        mCornerRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        int densityDpi = newConfig.densityDpi;
+        if (mDensity != densityDpi) {
+            mDensity = densityDpi;
+            initView();
+        }
     }
 
     @Override
@@ -145,6 +166,28 @@
                     mDrawable.draw(canvas);
                     canvas.restore();
                 }
+                // We also need to draw the rounded corners of the background
+                canvas.save();
+                canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+                        mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+                        mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+                        mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.right - mCornerRadius,
+                        mExcludedRect.bottom - mCornerRadius,
+                        mExcludedRect.right, mExcludedRect.bottom);
+                mDrawable.draw(canvas);
+                canvas.restore();
             }
         }
     }
@@ -252,6 +295,13 @@
         return false;
     }
 
+    /**
+     * It might look counterintuitive to have another method to set the alpha instead of
+     * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+     * optimizing blend modes, so it makes sense.
+     *
+     * @param alpha Gradient alpha from 0 to 1.
+     */
     public void setViewAlpha(float alpha) {
         if (alpha != mViewAlpha) {
             mViewAlpha = alpha;
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index f5c77f2..64c52ed 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -369,7 +369,7 @@
     private void onFacetClicked(Intent intent, int index) {
         String packageName = intent.getPackage();
 
-        if (packageName == null) {
+        if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
             return;
         }
 
diff --git a/com/android/systemui/statusbar/notification/AnimatableProperty.java b/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 0000000..d7b211f
--- /dev/null
+++ b/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+    int getAnimationStartTag();
+
+    int getAnimationEndTag();
+
+    int getAnimatorTag();
+
+    Property getProperty();
+
+    static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+            Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+        Property<T, Float> property = new FloatProperty<T>(name) {
+
+            @Override
+            public Float get(T object) {
+                return getter.apply(object);
+            }
+
+            @Override
+            public void setValue(T object, float value) {
+                setter.accept(object, value);
+            }
+        };
+        return new AnimatableProperty() {
+            @Override
+            public int getAnimationStartTag() {
+                return startValueTag;
+            }
+
+            @Override
+            public int getAnimationEndTag() {
+                return endValueTag;
+            }
+
+            @Override
+            public int getAnimatorTag() {
+                return animatorTag;
+            }
+
+            @Override
+            public Property getProperty() {
+                return property;
+            }
+        };
+    }
+}
diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index fc420eb..27defca 100644
--- a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -89,6 +89,7 @@
 
     private void transformViewInternal(MessagingLayoutTransformState mlt,
             float transformationAmount, boolean to) {
+        ensureVisible();
         ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
                 mMessagingLayout.getMessagingGroups());
         ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
@@ -332,6 +333,7 @@
 
     @Override
     public void setVisible(boolean visible, boolean force) {
+        super.setVisible(visible, force);
         resetTransformedView();
         ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
         for (int i = 0; i < ownGroups.size(); i++) {
diff --git a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..66682e4 100644
--- a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
+import android.os.Build;
 import android.view.View;
 
 import com.android.systemui.R;
@@ -37,6 +38,7 @@
     private final Paint mGreyPaint = new Paint();
     private boolean mIsLegacy;
     private int mLegacyColor;
+    private boolean mBeforeP;
 
     protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -115,4 +117,17 @@
         super.setLegacy(legacy);
         mIsLegacy = legacy;
     }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        // Before P we ensure that they are now drawing inside out content bounds since we inset
+        // the view. If they target P, then we don't have that guarantee and we need to be safe.
+        return !mBeforeP;
+    }
+
+    @Override
+    public void onContentUpdated(ExpandableNotificationRow row) {
+        super.onContentUpdated(row);
+        mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+    }
 }
diff --git a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a1..060e6d6 100644
--- a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -58,6 +58,11 @@
 
     @Override
     public boolean isDimmable() {
-        return false;
+        return getCustomBackgroundColor() == 0;
+    }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return true;
     }
 }
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index fd085d9..e07112f 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@
         updateActionOffset();
     }
 
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+    }
+
     private void updateActionOffset() {
         if (mActionsContainer != null) {
             // We should never push the actions higher than they are in the headsup view.
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 1cd5f15..8a767bb 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@
     public int getMinLayoutHeight() {
         return 0;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        return false;
+    }
 }
diff --git a/com/android/systemui/statusbar/notification/PropertyAnimator.java b/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba943..92dcc9e 100644
--- a/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@
  */
 public class PropertyAnimator {
 
+    public static <T extends View> void setProperty(final T view,
+            AnimatableProperty animatableProperty, float newEndValue,
+            AnimationProperties properties, boolean animated) {
+        int animatorTag = animatableProperty.getAnimatorTag();
+        ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+        if (previousAnimator != null || animated) {
+            startAnimation(view, animatableProperty, newEndValue, properties);
+        } else {
+            // no new animation needed, let's just apply the value
+            animatableProperty.getProperty().set(view, newEndValue);
+        }
+    }
+
     public static <T extends View> void startAnimation(final T view,
             AnimatableProperty animatableProperty, float newEndValue,
             AnimationProperties properties) {
@@ -102,10 +115,4 @@
         view.setTag(animationEndTag, newEndValue);
     }
 
-    public interface AnimatableProperty {
-        int getAnimationStartTag();
-        int getAnimationEndTag();
-        int getAnimatorTag();
-        Property getProperty();
-    }
 }
diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java
index ad07af0..dec5303 100644
--- a/com/android/systemui/statusbar/notification/TransformState.java
+++ b/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,18 +95,22 @@
     public void transformViewFrom(TransformState otherState, float transformationAmount) {
         mTransformedView.animate().cancel();
         if (sameAs(otherState)) {
-            if (mTransformedView.getVisibility() == View.INVISIBLE
-                    || mTransformedView.getAlpha() != 1.0f) {
-                // We have the same content, lets show ourselves
-                mTransformedView.setAlpha(1.0f);
-                mTransformedView.setVisibility(View.VISIBLE);
-            }
+            ensureVisible();
         } else {
             CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
         }
         transformViewFullyFrom(otherState, transformationAmount);
     }
 
+    protected void ensureVisible() {
+        if (mTransformedView.getVisibility() == View.INVISIBLE
+                || mTransformedView.getAlpha() != 1.0f) {
+            // We have the same content, lets show ourselves
+            mTransformedView.setAlpha(1.0f);
+            mTransformedView.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
         transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
     }
diff --git a/com/android/systemui/statusbar/phone/DozeParameters.java b/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b..3f57c2f 100644
--- a/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@
     public void dump(PrintWriter pw) {
         pw.println("  DozeParameters:");
         pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
-        pw.print("    getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
-        pw.print("    getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
-        pw.print("    getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
-        pw.print("    getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+        pw.print("    getPulseDuration(): "); pw.println(getPulseDuration());
+        pw.print("    getPulseInDuration(): "); pw.println(getPulseInDuration());
         pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
         pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
         pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@
         return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
     }
 
-    public int getPulseDuration(boolean pickup) {
-        return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+    public int getPulseDuration() {
+        return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
     }
 
-    public int getPulseInDuration(boolean pickupOrDoubleTap) {
-        return pickupOrDoubleTap
-                ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
-                : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+    public int getPulseInDuration() {
+        return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
     }
 
     public int getPulseVisibleDuration() {
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb849..1011383 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
-import android.view.animation.Interpolator;
 
-import com.android.systemui.Interpolators;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 
@@ -40,74 +35,59 @@
     private final Handler mHandler = new Handler();
     private final ScrimController mScrimController;
 
-    private final Context mContext;
-
     private boolean mDozing;
     private DozeHost.PulseCallback mPulseCallback;
     private int mPulseReason;
-    private Animator mInFrontAnimator;
-    private Animator mBehindAnimator;
-    private float mInFrontTarget;
-    private float mBehindTarget;
-    private boolean mDozingAborted;
-    private boolean mWakeAndUnlocking;
     private boolean mFullyPulsing;
 
-    private float mAodFrontScrimOpacity = 0;
-    private Runnable mSetDozeInFrontAlphaDelayed;
+    private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+        @Override
+        public void onDisplayBlanked() {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+                        + DozeLog.pulseReasonToString(mPulseReason));
+            }
+            if (!mDozing) {
+                return;
+            }
+
+            // Signal that the pulse is ready to turn the screen on and draw.
+            pulseStarted();
+        }
+
+        @Override
+        public void onFinished() {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+            }
+            if (!mDozing) {
+                return;
+            }
+            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+            mHandler.postDelayed(mPulseOutExtended,
+                    mDozeParameters.getPulseVisibleDurationExtended());
+            mFullyPulsing = true;
+        }
+
+        /**
+         * Transition was aborted before it was over.
+         */
+        @Override
+        public void onCancelled() {
+            pulseFinished();
+        }
+    };
 
     public DozeScrimController(ScrimController scrimController, Context context) {
-        mContext = context;
         mScrimController = scrimController;
         mDozeParameters = new DozeParameters(context);
     }
 
-    public void setDozing(boolean dozing, boolean animate) {
+    public void setDozing(boolean dozing) {
         if (mDozing == dozing) return;
         mDozing = dozing;
-        mWakeAndUnlocking = false;
-        if (mDozing) {
-            mDozingAborted = false;
-            abortAnimations();
-            mScrimController.setDozeBehindAlpha(1f);
-            setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
-        } else {
+        if (!mDozing) {
             cancelPulsing();
-            if (animate) {
-                startScrimAnimation(false /* inFront */, 0f /* target */,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION,
-                        Interpolators.LINEAR_OUT_SLOW_IN);
-                startScrimAnimation(true /* inFront */, 0f /* target */,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION,
-                        Interpolators.LINEAR_OUT_SLOW_IN);
-            } else {
-                abortAnimations();
-                mScrimController.setDozeBehindAlpha(0f);
-                setDozeInFrontAlpha(0f);
-            }
-        }
-    }
-
-    /**
-     * Set the opacity of the front scrim when showing AOD1
-     *
-     * Used to emulate lower brightness values than the hardware supports natively.
-     */
-    public void setAodDimmingScrim(float scrimOpacity) {
-        mAodFrontScrimOpacity = scrimOpacity;
-        if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
-                && mDozeParameters.getAlwaysOn()) {
-            setDozeInFrontAlpha(mAodFrontScrimOpacity);
-        }
-    }
-
-    public void setWakeAndUnlocking() {
-        // Immediately abort the doze scrims in case of wake-and-unlock
-        // for pulsing so the Keyguard fade-out animation scrim can take over.
-        if (!mWakeAndUnlocking) {
-            mWakeAndUnlocking = true;
-            mScrimController.setDozeBehindAlpha(0f);
-            setDozeInFrontAlpha(0f);
         }
     }
 
@@ -118,37 +98,21 @@
         }
 
         if (!mDozing || mPulseCallback != null) {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+                        + (mPulseCallback != null));
+            }
             // Pulse suppressed.
             callback.onPulseFinished();
             return;
         }
 
-        // Begin pulse.  Note that it's very important that the pulse finished callback
+        // Begin pulse. Note that it's very important that the pulse finished callback
         // be invoked when we're done so that the caller can drop the pulse wakelock.
         mPulseCallback = callback;
         mPulseReason = reason;
-        setDozeInFrontAlpha(1f);
-        mHandler.post(mPulseIn);
-    }
 
-    /**
-     * Aborts pulsing immediately.
-     */
-    public void abortPulsing() {
-        cancelPulsing();
-        if (mDozing && !mWakeAndUnlocking) {
-            mScrimController.setDozeBehindAlpha(1f);
-            setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
-                    ? mAodFrontScrimOpacity : 1f);
-        }
-    }
-
-    /**
-     * Aborts dozing immediately.
-     */
-    public void abortDoze() {
-        mDozingAborted = true;
-        abortPulsing();
+        mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
     }
 
     public void pulseOutNow() {
@@ -157,17 +121,6 @@
         }
     }
 
-    public void onScreenTurnedOn() {
-        if (isPulsing()) {
-            final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
-                    || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
-            startScrimAnimation(true /* inFront */, 0f,
-                    mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
-                    pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
-                    mPulseInFinished);
-        }
-    }
-
     public boolean isPulsing() {
         return mPulseCallback != null;
     }
@@ -181,11 +134,9 @@
     }
 
     private void cancelPulsing() {
-        if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
         if (mPulseCallback != null) {
+            if (DEBUG) Log.d(TAG, "Cancel pulsing");
             mFullyPulsing = false;
-            mHandler.removeCallbacks(mPulseIn);
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
             pulseFinished();
@@ -193,151 +144,20 @@
     }
 
     private void pulseStarted() {
+        DozeLog.tracePulseStart(mPulseReason);
         if (mPulseCallback != null) {
             mPulseCallback.onPulseStarted();
         }
     }
 
     private void pulseFinished() {
+        DozeLog.tracePulseFinish();
         if (mPulseCallback != null) {
             mPulseCallback.onPulseFinished();
             mPulseCallback = null;
         }
     }
 
-    private void abortAnimations() {
-        if (mInFrontAnimator != null) {
-            mInFrontAnimator.cancel();
-        }
-        if (mBehindAnimator != null) {
-            mBehindAnimator.cancel();
-        }
-    }
-
-    private void startScrimAnimation(final boolean inFront, float target, long duration,
-            Interpolator interpolator) {
-        startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
-    }
-
-    private void startScrimAnimation(final boolean inFront, float target, long duration,
-            Interpolator interpolator, final Runnable endRunnable) {
-        Animator current = getCurrentAnimator(inFront);
-        if (current != null) {
-            float currentTarget = getCurrentTarget(inFront);
-            if (currentTarget == target) {
-                return;
-            }
-            current.cancel();
-        }
-        ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
-        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                setDozeAlpha(inFront, value);
-            }
-        });
-        anim.setInterpolator(interpolator);
-        anim.setDuration(duration);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                setCurrentAnimator(inFront, null);
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-            }
-        });
-        anim.start();
-        setCurrentAnimator(inFront, anim);
-        setCurrentTarget(inFront, target);
-    }
-
-    private float getCurrentTarget(boolean inFront) {
-        return inFront ? mInFrontTarget : mBehindTarget;
-    }
-
-    private void setCurrentTarget(boolean inFront, float target) {
-        if (inFront) {
-            mInFrontTarget = target;
-        } else {
-            mBehindTarget = target;
-        }
-    }
-
-    private Animator getCurrentAnimator(boolean inFront) {
-        return inFront ? mInFrontAnimator : mBehindAnimator;
-    }
-
-    private void setCurrentAnimator(boolean inFront, Animator animator) {
-        if (inFront) {
-            mInFrontAnimator = animator;
-        } else {
-            mBehindAnimator = animator;
-        }
-    }
-
-    private void setDozeAlpha(boolean inFront, float alpha) {
-        if (mWakeAndUnlocking) {
-            return;
-        }
-        if (inFront) {
-            mScrimController.setDozeInFrontAlpha(alpha);
-        } else {
-            mScrimController.setDozeBehindAlpha(alpha);
-        }
-    }
-
-    private float getDozeAlpha(boolean inFront) {
-        return inFront
-                ? mScrimController.getDozeInFrontAlpha()
-                : mScrimController.getDozeBehindAlpha();
-    }
-
-    private void setDozeInFrontAlpha(float opacity) {
-        setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
-    }
-
-    private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
-        if (mSetDozeInFrontAlphaDelayed != null) {
-            mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
-            mSetDozeInFrontAlphaDelayed = null;
-        }
-        if (delayMs <= 0) {
-            mScrimController.setDozeInFrontAlpha(opacity);
-        } else {
-            mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
-                setDozeInFrontAlpha(opacity);
-            }, delayMs);
-        }
-    }
-
-    private final Runnable mPulseIn = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
-                    + DozeLog.pulseReasonToString(mPulseReason));
-            if (!mDozing) return;
-            DozeLog.tracePulseStart(mPulseReason);
-
-            // Signal that the pulse is ready to turn the screen on and draw.
-            pulseStarted();
-        }
-    };
-
-    private final Runnable mPulseInFinished = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
-            if (!mDozing) return;
-            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
-            mHandler.postDelayed(mPulseOutExtended,
-                    mDozeParameters.getPulseVisibleDurationExtended());
-            mFullyPulsing = true;
-        }
-    };
-
     private final Runnable mPulseOutExtended = new Runnable() {
         @Override
         public void run() {
@@ -354,38 +174,13 @@
             mHandler.removeCallbacks(mPulseOutExtended);
             if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
             if (!mDozing) return;
-            startScrimAnimation(true /* inFront */, 1,
-                    mDozeParameters.getPulseOutDuration(),
-                    Interpolators.ALPHA_IN, mPulseOutFinishing);
+            mScrimController.transitionTo(ScrimState.AOD,
+                    new ScrimController.Callback() {
+                        @Override
+                        public void onDisplayBlanked() {
+                            pulseFinished();
+                        }
+                    });
         }
     };
-
-    private final Runnable mPulseOutFinishing = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse out finished");
-            DozeLog.tracePulseFinish();
-            if (mDozeParameters.getAlwaysOn() && mDozing) {
-                // Setting power states can block rendering. For AOD, delay finishing the pulse and
-                // setting the power state until the fully black scrim had time to hit the
-                // framebuffer.
-                mHandler.postDelayed(mPulseOutFinished, 30);
-            } else {
-                mPulseOutFinished.run();
-            }
-        }
-    };
-
-    private final Runnable mPulseOutFinished = new Runnable() {
-        @Override
-        public void run() {
-            // Signal that the pulse is all finished so we can turn the screen off now.
-            DozeScrimController.this.pulseFinished();
-            if (mDozeParameters.getAlwaysOn()) {
-                // Setting power states can happen after we push out the frame. Make sure we
-                // stay fully opaque until the power state request reaches the lower levels.
-                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
-            }
-        }
-    };
-}
+}
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369db..80d4061 100644
--- a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@
     }
 
     private boolean pulsingOrAod() {
-        boolean pulsing = mDozeScrimController.isPulsing();
-        boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
-        return pulsing || dozingWithScreenOn;
+        final ScrimState scrimState = mScrimController.getState();
+        return scrimState == ScrimState.AOD
+                || scrimState == ScrimState.PULSING;
     }
 
     @Override
@@ -246,15 +246,12 @@
                             true /* allowEnterAnimation */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
-                    mDozeScrimController.abortDoze();
                 } else {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
                     mUpdateMonitor.awakenFromDream();
                 }
                 mStatusBarWindowManager.setStatusBarFocusable(false);
                 mKeyguardViewMediator.onWakeAndUnlocking();
-                mScrimController.setWakeAndUnlocking();
-                mDozeScrimController.setWakeAndUnlocking();
                 if (mStatusBar.getNavigationBarView() != null) {
                     mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
                 }
@@ -269,6 +266,7 @@
     }
 
     private void showBouncer() {
+        mScrimController.transitionTo(ScrimState.BOUNCER);
         mStatusBarKeyguardViewManager.animateCollapsePanels(
                 FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
         mPendingShowBouncer = false;
diff --git a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index a6691b1..da809c1 100644
--- a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -333,7 +333,7 @@
         return false;
     }
 
-    public void onOverlayChanged() {
+    public void onThemeChanged() {
         @ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
         @ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ?
                 R.color.dark_mode_icon_color_single_tone :
diff --git a/com/android/systemui/statusbar/phone/LockIcon.java b/com/android/systemui/statusbar/phone/LockIcon.java
index 5c9446c..34486db 100644
--- a/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@
                 }
                 break;
             case STATE_FACE_UNLOCK:
-                iconRes = com.android.internal.R.drawable.ic_account_circle;
+                iconRes = R.drawable.ic_account_circle;
                 break;
             case STATE_FINGERPRINT:
                 // If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index c950036..b81a3b0 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -80,7 +80,8 @@
 
     @Override
     protected boolean isLightsOut(int mode) {
-        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
+        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
+                && mode != MODE_WARNING);
     }
 
     public LightBarTransitionsController getLightTransitionsController() {
@@ -108,7 +109,9 @@
         // ok, everyone, stop it right there
         navButtons.animate().cancel();
 
-        final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
+        // Bump percentage by 10% if dark.
+        float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10;
+        final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
 
         if (!animate) {
             navButtons.setAlpha(navButtonsAlpha);
@@ -130,6 +133,9 @@
         for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
             buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
         }
+        if (mAutoDim) {
+            applyLightsOut(false, true);
+        }
     }
 
     private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c6..836efff 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -209,7 +209,7 @@
                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
-        if (mDark && child instanceof StatusBarIconView) {
+        if (child instanceof StatusBarIconView) {
             ((StatusBarIconView) child).setDark(mDark, false, 0);
         }
     }
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 86a8f41..17e3599 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -239,6 +239,7 @@
     private ValueAnimator mDarkAnimator;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private boolean mUserSetupComplete;
+    private int mQsNotificationTopPadding;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -307,6 +308,8 @@
                 R.dimen.max_notification_fadeout_height);
         mIndicationBottomPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_bottom_padding);
+        mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.qs_notification_keyguard_padding);
     }
 
     public void updateResources() {
@@ -330,7 +333,7 @@
         }
     }
 
-    public void onOverlayChanged() {
+    public void onThemeChanged() {
         // Re-inflate the status view group.
         int index = indexOfChild(mKeyguardStatusView);
         removeView(mKeyguardStatusView);
@@ -818,7 +821,7 @@
 
     private float getQsExpansionFraction() {
         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
-                / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+                / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
     }
 
     @Override
@@ -1361,7 +1364,7 @@
             // take the maximum and linearly interpolate with the panel expansion for a nice motion.
             int maxNotifications = mClockPositionResult.stackScrollerPadding
                     - mClockPositionResult.stackScrollerPaddingAdjustment;
-            int maxQs = getTempQsMaxExpansion();
+            int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
             int max = mStatusBarState == StatusBarState.KEYGUARD
                     ? Math.max(maxNotifications, maxQs)
                     : maxQs;
@@ -1375,7 +1378,7 @@
             // from a scrolled quick settings.
             return interpolate(getQsExpansionFraction(),
                     mNotificationStackScroller.getIntrinsicPadding(),
-                    mQsMaxExpansionHeight);
+                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
         } else {
             return mQsExpansionHeight;
         }
@@ -1544,7 +1547,7 @@
                         / (panelHeightQsExpanded - panelHeightQsCollapsed);
             }
             setQsExpansion(mQsMinExpansionHeight
-                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+                    + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
         }
         updateExpandedHeight(expandedHeight);
         updateHeader();
@@ -1566,14 +1569,6 @@
         }
     }
 
-    /**
-     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
-     *         collapsing QS / the panel when QS was scrolled
-     */
-    private int getTempQsMaxExpansion() {
-        return mQsMaxExpansionHeight;
-    }
-
     private int calculatePanelHeightShade() {
         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1591,10 @@
         }
         int maxQsHeight = mQsMaxExpansionHeight;
 
+        if (mKeyguardShowing) {
+            maxQsHeight += mQsNotificationTopPadding;
+        }
+
         // If an animation is changing the size of the QS panel, take the animated value.
         if (mQsSizeChangeAnimator != null) {
             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
diff --git a/com/android/systemui/statusbar/phone/ScrimController.java b/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3..3a36776 100644
--- a/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.os.Trace;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,12 +36,14 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
 import com.android.internal.graphics.ColorUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
@@ -56,33 +63,54 @@
  * security method gets shown).
  */
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
-        OnHeadsUpChangedListener, OnColorsChangedListener {
+        OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+    private static final String TAG = "ScrimController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     public static final long ANIMATION_DURATION = 220;
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
             = new PathInterpolator(0f, 0, 0.7f, 1f);
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
-    // Default alpha value for most scrims, if unsure use this constant
+    /**
+     * Default alpha value for most scrims.
+     */
     public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
-    // A scrim varies its opacity based on a busyness factor, for example
-    // how many notifications are currently visible.
+    /**
+     * A scrim varies its opacity based on a busyness factor, for example
+     * how many notifications are currently visible.
+     */
     public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+    /**
+     * The most common scrim, the one under the keyguard.
+     */
     protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+    /**
+     * We fade out the bottom scrim when the bouncer is visible.
+     */
     protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
-    private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
-    private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
-    private static final int TAG_KEY_ANIM = R.id.scrim;
+    /**
+     * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+     */
+    protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+    static final int TAG_KEY_ANIM = R.id.scrim;
+    static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
     private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
     private static final float NOT_INITIALIZED = -1;
 
-    private final LightBarController mLightBarController;
+    private ScrimState mState = ScrimState.UNINITIALIZED;
+    private final Context mContext;
     protected final ScrimView mScrimBehind;
     protected final ScrimView mScrimInFront;
-    private final UnlockMethodCache mUnlockMethodCache;
     private final View mHeadsUpScrim;
+    private final LightBarController mLightBarController;
+    private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final DozeParameters mDozeParameters;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -94,61 +122,53 @@
     protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
     protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
 
-    protected boolean mKeyguardShowing;
     private float mFraction;
 
     private boolean mDarkenWhileDragging;
-    protected boolean mBouncerShowing;
-    protected boolean mBouncerIsKeyguard = false;
-    private boolean mWakeAndUnlocking;
     protected boolean mAnimateChange;
     private boolean mUpdatePending;
     private boolean mTracking;
     private boolean mAnimateKeyguardFadingOut;
-    protected long mDurationOverride = -1;
+    protected long mAnimationDuration = -1;
     private long mAnimationDelay;
     private Runnable mOnAnimationFinished;
     private boolean mDeferFinishedListener;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
-    private boolean mDozing;
-    private float mDozeInFrontAlpha;
-    private float mDozeBehindAlpha;
     private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
     private float mCurrentBehindAlpha = NOT_INITIALIZED;
-    private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+    private int mCurrentInFrontTint;
+    private int mCurrentBehindTint;
     private int mPinnedHeadsUpCount;
     private float mTopHeadsUpDragAmount;
     private View mDraggedHeadsUpView;
-    private boolean mForceHideScrims;
-    private boolean mSkipFirstFrame;
-    private boolean mDontAnimateBouncerChanges;
     private boolean mKeyguardFadingOutInProgress;
-    private boolean mAnimatingDozeUnlock;
     private ValueAnimator mKeyguardFadeoutAnimation;
-    /** Wake up from AOD transition is starting; need fully opaque front scrim */
-    private boolean mWakingUpFromAodStarting;
-    /** Wake up from AOD transition is in progress; need black tint */
-    private boolean mWakingUpFromAodInProgress;
-    /** Wake up from AOD transition is animating; need to reset when animation finishes */
-    private boolean mWakingUpFromAodAnimationRunning;
-    private boolean mScrimsVisble;
+    private boolean mScrimsVisible;
     private final Consumer<Boolean> mScrimVisibleListener;
+    private boolean mBlankScreen;
+    private boolean mScreenBlankingCallbackCalled;
+    private Callback mCallback;
+
+    private final WakeLock mWakeLock;
+    private boolean mWakeLockHeld;
 
     public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
-            ScrimView scrimInFront, View headsUpScrim,
-            Consumer<Boolean> scrimVisibleListener) {
+            ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+            DozeParameters dozeParameters) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mHeadsUpScrim = headsUpScrim;
         mScrimVisibleListener = scrimVisibleListener;
-        final Context context = scrimBehind.getContext();
-        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mContext = scrimBehind.getContext();
+        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mLightBarController = lightBarController;
-        mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mWakeLock = createWakeLock();
         // Scrim alpha is initially set to the value on the resource but might be changed
         // to make sure that text on top of it is legible.
         mScrimBehindAlpha = mScrimBehindAlphaResValue;
+        mDozeParameters = dozeParameters;
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
@@ -158,22 +178,90 @@
                 ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mNeedsDrawableColorUpdate = true;
 
+        final ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+            states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+        }
+        mState = ScrimState.UNINITIALIZED;
+
         updateHeadsUpScrim(false);
         updateScrims();
     }
 
-    public void setKeyguardShowing(boolean showing) {
-        mKeyguardShowing = showing;
+    public void transitionTo(ScrimState state) {
+        transitionTo(state, null);
+    }
 
-        // Showing/hiding the keyguard means that scrim colors have to be switched
-        mNeedsDrawableColorUpdate = true;
-        scheduleUpdate();
+    public void transitionTo(ScrimState state, Callback callback) {
+        if (state == mState) {
+            return;
+        } else if (DEBUG) {
+            Log.d(TAG, "State changed to: " + state);
+        }
+
+        if (state == ScrimState.UNINITIALIZED) {
+            throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+        }
+
+        if (mCallback != null) {
+            mCallback.onCancelled();
+        }
+        mCallback = callback;
+
+        state.prepare(mState);
+        mScreenBlankingCallbackCalled = false;
+        mAnimationDelay = 0;
+        mBlankScreen = state.getBlanksScreen();
+        mAnimateChange = state.getAnimateChange();
+        mAnimationDuration = state.getAnimationDuration();
+        mCurrentInFrontTint = state.getFrontTint();
+        mCurrentBehindTint = state.getBehindTint();
+        mCurrentInFrontAlpha = state.getFrontAlpha();
+        mCurrentBehindAlpha = state.getBehindAlpha();
+
+        // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+        // to do the same when you're just showing the brightness mirror.
+        mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+        if (mKeyguardFadeoutAnimation != null) {
+            mKeyguardFadeoutAnimation.cancel();
+        }
+
+        mState = state;
+
+        // Do not let the device sleep until we're done with all animations
+        if (!mWakeLockHeld) {
+            if (mWakeLock != null) {
+                mWakeLockHeld = true;
+                mWakeLock.acquire();
+            } else {
+                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+            }
+        }
+
+        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
+            scheduleUpdate();
+        } else {
+            // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+            // with too many things at this case, in order to not skip the initial frames.
+            mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+            mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+        }
+    }
+
+    public ScrimState getState() {
+        return mState;
     }
 
     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
             float scrimBehindAlphaUnlocking) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
         mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+        }
         scheduleUpdate();
     }
 
@@ -186,131 +274,59 @@
         mTracking = false;
     }
 
+    /**
+     * Current state of the shade expansion when pulling it from the top.
+     * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+     *
+     * The expansion fraction is tied to the scrim opacity.
+     *
+     * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+     */
     public void setPanelExpansion(float fraction) {
         if (mFraction != fraction) {
             mFraction = fraction;
-            scheduleUpdate();
+
+            if (mState == ScrimState.UNLOCKED) {
+                // Darken scrim as you pull down the shade when unlocked
+                float behindFraction = getInterpolatedFraction();
+                behindFraction = (float) Math.pow(behindFraction, 0.8f);
+                mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+                mCurrentInFrontAlpha = 0;
+            } else if (mState == ScrimState.KEYGUARD) {
+                if (mUpdatePending) {
+                    return;
+                }
+
+                // Either darken of make the scrim transparent when you
+                // pull down the shade
+                float interpolatedFract = getInterpolatedFraction();
+                if (mDarkenWhileDragging) {
+                    mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+                            mScrimBehindAlphaKeyguard, interpolatedFract);
+                    mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+                } else {
+                    mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+                            interpolatedFract);
+                    mCurrentInFrontAlpha = 0;
+                }
+            } else {
+                Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+                return;
+            }
+
             if (mPinnedHeadsUpCount != 0) {
                 updateHeadsUpScrim(false);
             }
-            if (mKeyguardFadeoutAnimation != null && mTracking) {
-                mKeyguardFadeoutAnimation.cancel();
-            }
+
+            updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+            updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
         }
     }
 
-    public void setBouncerShowing(boolean showing) {
-        mBouncerShowing = showing;
-        mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
-        scheduleUpdate();
-    }
-
-    /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
-    public void prepareWakeUpFromAod() {
-        if (mWakingUpFromAodInProgress) {
-            return;
-        }
-        mWakingUpFromAodInProgress = true;
-        mWakingUpFromAodStarting = true;
-        mAnimateChange = false;
-        scheduleUpdate();
-        onPreDraw();
-    }
-
-    /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
-    public void wakeUpFromAod() {
-        if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
-            // Wake and unlocking has a separate transition that must not be interfered with.
-            mWakingUpFromAodStarting = false;
-            mWakingUpFromAodInProgress = false;
-            return;
-        }
-        if (mWakingUpFromAodStarting) {
-            mWakingUpFromAodInProgress = true;
-            mWakingUpFromAodStarting = false;
-            mAnimateChange = true;
-            scheduleUpdate();
-        }
-    }
-
-    public void setWakeAndUnlocking() {
-        mWakeAndUnlocking = true;
-        mAnimatingDozeUnlock = true;
-        mWakingUpFromAodStarting = false;
-        mWakingUpFromAodInProgress = false;
-        scheduleUpdate();
-    }
-
-    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
-            boolean skipFirstFrame) {
-        mWakeAndUnlocking = false;
-        mAnimateKeyguardFadingOut = true;
-        mDurationOverride = duration;
-        mAnimationDelay = delay;
-        mAnimateChange = true;
-        mSkipFirstFrame = skipFirstFrame;
-        mOnAnimationFinished = onAnimationFinished;
-
-        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
-            scheduleUpdate();
-
-            // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
-            // the changes we just scheduled.
-            onPreDraw();
-        } else {
-
-            // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
-            // with too many things in this case, in order to not skip the initial frames.
-            mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
-        }
-    }
-
-    public void abortKeyguardFadingOut() {
-        if (mAnimateKeyguardFadingOut) {
-            endAnimateKeyguardFadingOut(true /* force */);
-        }
-    }
-
-    public void animateKeyguardUnoccluding(long duration) {
-        mAnimateChange = false;
-        setScrimBehindAlpha(0f);
-        mAnimateChange = true;
-        scheduleUpdate();
-        mDurationOverride = duration;
-    }
-
-    public void animateGoingToFullShade(long delay, long duration) {
-        mDurationOverride = duration;
-        mAnimationDelay = delay;
-        mAnimateChange = true;
-        scheduleUpdate();
-    }
-
-    public void setDozing(boolean dozing) {
-        if (mDozing != dozing) {
-            mDozing = dozing;
-            scheduleUpdate();
-        }
-    }
-
-    public void setDozeInFrontAlpha(float alpha) {
-        mDozeInFrontAlpha = alpha;
-        updateScrimColor(mScrimInFront);
-    }
-
-    public void setDozeBehindAlpha(float alpha) {
-        mDozeBehindAlpha = alpha;
-        updateScrimColor(mScrimBehind);
-    }
-
-    public float getDozeBehindAlpha() {
-        return mDozeBehindAlpha;
-    }
-
-    public float getDozeInFrontAlpha() {
-        return mDozeInFrontAlpha;
-    }
-
+    /**
+     * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+     * @param notificationCount Number of visible notifications.
+     */
     public void setNotificationCount(int notificationCount) {
         final float maxNotificationDensity = 3;
         float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@
                 notificationDensity);
         if (mScrimBehindAlphaKeyguard != newAlpha) {
             mScrimBehindAlphaKeyguard = newAlpha;
-            mAnimateChange = true;
-            scheduleUpdate();
-        }
-    }
 
-    private float getScrimInFrontAlpha() {
-        return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
-                ? SCRIM_IN_FRONT_ALPHA_LOCKED
-                : SCRIM_IN_FRONT_ALPHA;
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+                scheduleUpdate();
+            }
+        }
     }
 
     /**
@@ -352,7 +364,7 @@
         if (mNeedsDrawableColorUpdate) {
             mNeedsDrawableColorUpdate = false;
             final GradientColors currentScrimColors;
-            if (mKeyguardShowing) {
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
                 // Always animate color changes if we're seeing the keyguard
                 mScrimInFront.setColors(mLockColors, true /* animated */);
                 mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
-        if (mAnimateKeyguardFadingOut || mForceHideScrims) {
-            setScrimInFrontAlpha(0f);
-            setScrimBehindAlpha(0f);
-        } else if (mWakeAndUnlocking) {
-            // During wake and unlock, we first hide everything behind a black scrim, which then
-            // gets faded out from animateKeyguardFadingOut. This must never be animated.
-            mAnimateChange = false;
-            if (mDozing) {
-                setScrimInFrontAlpha(0f);
-                setScrimBehindAlpha(1f);
-            } else {
-                setScrimInFrontAlpha(1f);
-                setScrimBehindAlpha(0f);
-            }
-        } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
-            updateScrimNormal();
-            setScrimInFrontAlpha(0);
-        } else {
-            updateScrimKeyguard();
-        }
-        mAnimateChange = false;
+        setScrimInFrontAlpha(mCurrentInFrontAlpha);
+        setScrimBehindAlpha(mCurrentBehindAlpha);
+
         dispatchScrimsVisible();
     }
 
     private void dispatchScrimsVisible() {
         boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
 
-        if (mScrimsVisble != scrimsVisible) {
-            mScrimsVisble = scrimsVisible;
+        if (mScrimsVisible != scrimsVisible) {
+            mScrimsVisible = scrimsVisible;
 
             mScrimVisibleListener.accept(scrimsVisible);
         }
     }
 
-    private void updateScrimKeyguard() {
-        if (mTracking && mDarkenWhileDragging) {
-            float behindFraction = Math.max(0, Math.min(mFraction, 1));
-            float fraction = 1 - behindFraction;
-            fraction = (float) Math.pow(fraction, 0.8f);
-            behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
-            setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
-        } else if (mBouncerShowing && !mBouncerIsKeyguard) {
-            setScrimInFrontAlpha(getScrimInFrontAlpha());
-            updateScrimNormal();
-        } else if (mBouncerShowing) {
-            setScrimInFrontAlpha(0f);
-            setScrimBehindAlpha(mScrimBehindAlpha);
-        } else {
-            float fraction = Math.max(0, Math.min(mFraction, 1));
-            if (mWakingUpFromAodStarting) {
-                setScrimInFrontAlpha(1f);
-            } else {
-                setScrimInFrontAlpha(0f);
-            }
-            setScrimBehindAlpha(fraction
-                    * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
-                    + mScrimBehindAlphaUnlocking);
-        }
-    }
-
-    private void updateScrimNormal() {
+    private float getInterpolatedFraction() {
         float frac = mFraction;
         // let's start this 20% of the way down the screen
         frac = frac * 1.2f - 0.2f;
         if (frac <= 0) {
-            setScrimBehindAlpha(0);
+            return 0;
         } else {
             // woo, special effects
-            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
-            setScrimBehindAlpha(k * mScrimBehindAlpha);
+            return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
         }
     }
 
@@ -455,102 +421,76 @@
 
     private void setScrimInFrontAlpha(float alpha) {
         setScrimAlpha(mScrimInFront, alpha);
-        if (alpha == 0f) {
-            mScrimInFront.setClickable(false);
-        } else {
-            // Eat touch events (unless dozing).
-            mScrimInFront.setClickable(!mDozing);
-        }
     }
 
     private void setScrimAlpha(View scrim, float alpha) {
-        updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
-    }
-
-    protected float getDozeAlpha(View scrim) {
-        return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
-    }
-
-    protected float getCurrentScrimAlpha(View scrim) {
-        return scrim == mScrimBehind ? mCurrentBehindAlpha
-                : scrim == mScrimInFront ? mCurrentInFrontAlpha
-                : mCurrentHeadsUpAlpha;
-    }
-
-    private void setCurrentScrimAlpha(View scrim, float alpha) {
-        if (scrim == mScrimBehind) {
-            mCurrentBehindAlpha = alpha;
-            mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
-        } else if (scrim == mScrimInFront) {
-            mCurrentInFrontAlpha = alpha;
+        if (alpha == 0f) {
+            scrim.setClickable(false);
         } else {
-            alpha = Math.max(0.0f, Math.min(1.0f, alpha));
-            mCurrentHeadsUpAlpha = alpha;
+            // Eat touch events (unless dozing).
+            scrim.setClickable(!(mState == ScrimState.AOD));
         }
+        updateScrim(mAnimateChange, scrim, alpha);
     }
 
-    private void updateScrimColor(View scrim) {
-        float alpha1 = getCurrentScrimAlpha(scrim);
+    private void updateScrimColor(View scrim, float alpha, int tint) {
+        alpha = Math.max(0, Math.min(1.0f, alpha));
         if (scrim instanceof ScrimView) {
             ScrimView scrimView = (ScrimView) scrim;
-            float dozeAlpha = getDozeAlpha(scrim);
-            float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
-            alpha = Math.max(0, Math.min(1.0f, alpha));
-            scrimView.setViewAlpha(alpha);
 
             Trace.traceCounter(Trace.TRACE_TAG_APP,
                     scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
                     (int) (alpha * 255));
 
-            int dozeTint = Color.TRANSPARENT;
-
-            boolean dozing = mAnimatingDozeUnlock || mDozing;
-            boolean frontScrimDozing = mWakingUpFromAodInProgress;
-            if (dozing || frontScrimDozing && scrim == mScrimInFront) {
-                dozeTint = Color.BLACK;
-            }
             Trace.traceCounter(Trace.TRACE_TAG_APP,
                     scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
-                    dozeTint == Color.BLACK ? 1 : 0);
+                    Color.alpha(tint));
 
-            scrimView.setTint(dozeTint);
+            scrimView.setTint(tint);
+            scrimView.setViewAlpha(alpha);
         } else {
-            scrim.setAlpha(alpha1);
+            scrim.setAlpha(alpha);
         }
         dispatchScrimsVisible();
     }
 
-    private void startScrimAnimation(final View scrim, float target) {
-        float current = getCurrentScrimAlpha(scrim);
-        ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+    private int getCurrentScrimTint(View scrim) {
+        return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+    }
+
+    private void startScrimAnimation(final View scrim, float current, float target) {
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+                Color.TRANSPARENT;
         anim.addUpdateListener(animation -> {
-            float alpha = (float) animation.getAnimatedValue();
-            setCurrentScrimAlpha(scrim, alpha);
-            updateScrimColor(scrim);
+            final float animAmount = (float) animation.getAnimatedValue();
+            final int finalScrimTint = scrim == mScrimInFront ?
+                    mCurrentInFrontTint : mCurrentBehindTint;
+            float alpha = MathUtils.lerp(current, target, animAmount);
+            int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+            updateScrimColor(scrim, alpha, tint);
             dispatchScrimsVisible();
         });
         anim.setInterpolator(getInterpolator());
         anim.setStartDelay(mAnimationDelay);
-        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+        anim.setDuration(mAnimationDuration);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (mKeyguardFadingOutInProgress) {
+                    mKeyguardFadeoutAnimation = null;
+                    mKeyguardFadingOutInProgress = false;
+                }
+                onFinished();
+
+                scrim.setTag(TAG_KEY_ANIM, null);
+                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
+                dispatchScrimsVisible();
+
                 if (!mDeferFinishedListener && mOnAnimationFinished != null) {
                     mOnAnimationFinished.run();
                     mOnAnimationFinished = null;
                 }
-                if (mKeyguardFadingOutInProgress) {
-                    mKeyguardFadeoutAnimation = null;
-                    mKeyguardFadingOutInProgress = false;
-                    mAnimatingDozeUnlock = false;
-                }
-                if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
-                    mWakingUpFromAodAnimationRunning = false;
-                    mWakingUpFromAodInProgress = false;
-                }
-                scrim.setTag(TAG_KEY_ANIM, null);
-                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
-                dispatchScrimsVisible();
             }
         });
         anim.start();
@@ -558,12 +498,6 @@
             mKeyguardFadingOutInProgress = true;
             mKeyguardFadeoutAnimation = anim;
         }
-        if (mWakingUpFromAodInProgress) {
-            mWakingUpFromAodAnimationRunning = true;
-        }
-        if (mSkipFirstFrame) {
-            anim.setCurrentPlayTime(16);
-        }
         scrim.setTag(TAG_KEY_ANIM, anim);
         scrim.setTag(TAG_KEY_ANIM_TARGET, target);
     }
@@ -582,19 +516,33 @@
     public boolean onPreDraw() {
         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
         mUpdatePending = false;
-        if (mDontAnimateBouncerChanges) {
-            mDontAnimateBouncerChanges = false;
+        if (mCallback != null) {
+            mCallback.onStart();
         }
         updateScrims();
-        mDurationOverride = -1;
-        mAnimationDelay = 0;
-        mSkipFirstFrame = false;
 
         // Make sure that we always call the listener even if we didn't start an animation.
         endAnimateKeyguardFadingOut(false /* force */);
         return true;
     }
 
+    private void onFinished() {
+        if (mWakeLockHeld) {
+            mWakeLock.release();
+            mWakeLockHeld = false;
+        }
+        if (mCallback != null) {
+            mCallback.onFinished();
+            mCallback = null;
+        }
+        // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+        // At the end of the animation we need to remove the tint.
+        if (mState == ScrimState.UNLOCKED) {
+            mCurrentInFrontTint = Color.TRANSPARENT;
+            mCurrentBehindTint = Color.TRANSPARENT;
+        }
+    }
+
     private void endAnimateKeyguardFadingOut(boolean force) {
         mAnimateKeyguardFadingOut = false;
         if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@
                 mOnAnimationFinished = null;
             }
             mKeyguardFadingOutInProgress = false;
-            if (!mWakeAndUnlocking || force)
-                mAnimatingDozeUnlock = false;
         }
     }
 
@@ -641,16 +587,19 @@
     }
 
     private void updateHeadsUpScrim(boolean animate) {
-        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
     }
 
-    private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
-        if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
-            return;
-        }
+    @VisibleForTesting
+    void setOnAnimationFinished(Runnable onAnimationFinished) {
+        mOnAnimationFinished = onAnimationFinished;
+    }
 
-        ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
-                TAG_KEY_ANIM);
+    private void updateScrim(boolean animate, View scrim, float alpha) {
+        final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+            : scrim.getAlpha();
+
+        ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
         float animEndValue = -1;
         if (previousAnimator != null) {
             if (animate || alpha == currentAlpha) {
@@ -664,9 +613,37 @@
                 animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
             }
         }
-        if (alpha != currentAlpha && alpha != animEndValue) {
+
+        final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+        if (mBlankScreen || blankingInProgress) {
+            if (!blankingInProgress) {
+                blankDisplay();
+            }
+            return;
+        } else if (!mScreenBlankingCallbackCalled) {
+            // Not blanking the screen. Letting the callback know that we're ready
+            // to replace what was on the screen before.
+            if (mCallback != null) {
+                mCallback.onDisplayBlanked();
+                mScreenBlankingCallbackCalled = true;
+            }
+        }
+
+        // TODO factor mLightBarController out of this class
+        if (scrim == mScrimBehind) {
+            mLightBarController.setScrimAlpha(alpha);
+        }
+
+        final ScrimView scrimView = scrim instanceof  ScrimView ? (ScrimView) scrim : null;
+        final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+        final boolean wantsTintUpdate = scrimView != null
+                && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+        if (wantsAlphaUpdate || wantsTintUpdate) {
             if (animate) {
-                startScrimAnimation(scrim, alpha);
+                final float fromAlpha = scrimView == null ? scrim.getAlpha()
+                        : scrimView.getViewAlpha();
+                startScrimAnimation(scrim, fromAlpha, alpha);
                 scrim.setTag(TAG_START_ALPHA, currentAlpha);
                 scrim.setTag(TAG_END_ALPHA, alpha);
             } else {
@@ -685,13 +662,62 @@
                     previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                 } else {
                     // update the alpha directly
-                    setCurrentScrimAlpha(scrim, alpha);
-                    updateScrimColor(scrim);
+                    updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+                    onFinished();
                 }
             }
+        } else {
+            onFinished();
         }
     }
 
+    private void blankDisplay() {
+        final float initialAlpha = mScrimInFront.getViewAlpha();
+        final int initialTint = mScrimInFront.getTint();
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        anim.addUpdateListener(animation -> {
+            final float amount = (float) animation.getAnimatedValue();
+            float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+            int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+            updateScrimColor(mScrimInFront, animAlpha, animTint);
+            dispatchScrimsVisible();
+        });
+        anim.setInterpolator(getInterpolator());
+        anim.setDuration(mDozeParameters.getPulseInDuration());
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCallback != null) {
+                    mCallback.onDisplayBlanked();
+                    mScreenBlankingCallbackCalled = true;
+                }
+                Runnable blankingCallback = () -> {
+                    mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+                    mBlankScreen = false;
+                    // Try again.
+                    updateScrims();
+                };
+
+                // Setting power states can happen after we push out the frame. Make sure we
+                // stay fully opaque until the power state request reaches the lower levels.
+                getHandler().postDelayed(blankingCallback, 100);
+
+            }
+        });
+        anim.start();
+        mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+        // Finish animation if we're already at its final state
+        if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+            anim.end();
+        }
+    }
+
+    @VisibleForTesting
+    protected Handler getHandler() {
+        return Handler.getMain();
+    }
+
     /**
      * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
      * the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +745,13 @@
         return alpha * expandFactor;
     }
 
-    public void forceHideScrims(boolean hide, boolean animated) {
-        mForceHideScrims = hide;
-        mAnimateChange = animated;
-        scheduleUpdate();
-    }
-
-    public void dontAnimateBouncerChangesUntilNextFrame() {
-        mDontAnimateBouncerChanges = true;
-    }
-
     public void setExcludedBackgroundArea(Rect area) {
         mScrimBehind.setExcludedArea(area);
     }
 
     public int getBackgroundColor() {
         int color = mLockColors.getMainColor();
-        return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+        return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
                 Color.red(color), Color.green(color), Color.blue(color));
     }
 
@@ -764,27 +780,41 @@
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
             mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_DARK, mKeyguardShowing);
+                    ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
     }
 
-    public void dump(PrintWriter pw) {
-        pw.println(" ScrimController:");
+    @VisibleForTesting
+    protected WakeLock createWakeLock() {
+         return new DelayedWakeLock(getHandler(),
+                WakeLock.createPartial(mContext, "Doze"));
+    }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(" ScrimController:");
+        pw.print(" state:"); pw.println(mState);
         pw.print("   frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
         pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
-        pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
 
         pw.print("   backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
         pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
-        pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
 
-        pw.print("   mBouncerShowing="); pw.println(mBouncerShowing);
         pw.print("   mTracking="); pw.println(mTracking);
-        pw.print("   mForceHideScrims="); pw.println(mForceHideScrims);
+    }
+
+    public interface Callback {
+        default void onStart() {
+        }
+        default void onDisplayBlanked() {
+        }
+        default void onFinished() {
+        }
+        default void onCancelled() {
+        }
     }
 }
diff --git a/com/android/systemui/statusbar/phone/ScrimState.java b/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 0000000..0db98f3
--- /dev/null
+++ b/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+    /**
+     * Initial state.
+     */
+    UNINITIALIZED,
+
+    /**
+     * On the lock screen.
+     */
+    KEYGUARD {
+
+        @Override
+        public void prepare(ScrimState previousState) {
+            // DisplayPowerManager will blank the screen, we'll just
+            // set our scrim to black in this frame to avoid flickering and
+            // fade it out afterwards.
+            mBlankScreen = previousState == ScrimState.AOD;
+            if (previousState == ScrimState.AOD) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
+            mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+            mCurrentInFrontAlpha = 0;
+        }
+    },
+
+    /**
+     * Showing password challenge.
+     */
+    BOUNCER {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+            mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+        }
+    },
+
+    /**
+     * Changing screen brightness from quick settings.
+     */
+    BRIGHTNESS_MIRROR {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 0;
+            mCurrentInFrontAlpha = 0;
+        }
+    },
+
+    /**
+     * Always on display or screen off.
+     */
+    AOD {
+        @Override
+        public void prepare(ScrimState previousState) {
+            if (previousState == ScrimState.PULSING) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
+            final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+            mBlankScreen = previousState == ScrimState.PULSING;
+            mCurrentBehindAlpha = 1;
+            mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+            mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindTint = Color.BLACK;
+            // DisplayPowerManager will blank the screen for us, we just need
+            // to set our state.
+            mAnimateChange = false;
+        }
+    },
+
+    /**
+     * When phone wakes up because you received a notification.
+     */
+    PULSING {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 1;
+            mCurrentInFrontAlpha = 0;
+            mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindTint = Color.BLACK;
+            mBlankScreen = true;
+            updateScrimColor(mScrimInFront, 1, Color.BLACK);
+        }
+    },
+
+    /**
+     * Unlocked on top of an app (launcher or any other activity.)
+     */
+    UNLOCKED {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 0;
+            mCurrentInFrontAlpha = 0;
+            mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+            if (previousState == ScrimState.AOD) {
+                // Fade from black to transparent when coming directly from AOD
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1, Color.BLACK);
+                // Scrims should still be black at the end of the transition.
+                mCurrentInFrontTint = Color.BLACK;
+                mCurrentBehindTint = Color.BLACK;
+                mBlankScreen = true;
+            } else {
+                // Scrims should still be black at the end of the transition.
+                mCurrentInFrontTint = Color.TRANSPARENT;
+                mCurrentBehindTint = Color.TRANSPARENT;
+                mBlankScreen = false;
+            }
+        }
+    };
+
+    boolean mBlankScreen = false;
+    long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+    int mCurrentInFrontTint = Color.TRANSPARENT;
+    int mCurrentBehindTint = Color.TRANSPARENT;
+    boolean mAnimateChange = true;
+    float mCurrentInFrontAlpha;
+    float mCurrentBehindAlpha;
+    float mAodFrontScrimAlpha;
+    float mScrimBehindAlphaKeyguard;
+    ScrimView mScrimInFront;
+    ScrimView mScrimBehind;
+    DozeParameters mDozeParameters;
+
+    public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+        mScrimInFront = scrimInFront;
+        mScrimBehind = scrimBehind;
+        mDozeParameters = dozeParameters;
+    }
+
+    public void prepare(ScrimState previousState) {
+    }
+
+    public float getFrontAlpha() {
+        return mCurrentInFrontAlpha;
+    }
+
+    public float getBehindAlpha() {
+        return mCurrentBehindAlpha;
+    }
+
+    public int getFrontTint() {
+        return mCurrentInFrontTint;
+    }
+
+    public int getBehindTint() {
+        return mCurrentBehindTint;
+    }
+
+    public long getAnimationDuration() {
+        return mAnimationDuration;
+    }
+
+    public boolean getBlanksScreen() {
+        return mBlankScreen;
+    }
+
+    public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+        Trace.traceCounter(Trace.TRACE_TAG_APP,
+                scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+                (int) (alpha * 255));
+
+        Trace.traceCounter(Trace.TRACE_TAG_APP,
+                scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+                Color.alpha(tint));
+
+        scrim.setTint(tint);
+        scrim.setViewAlpha(alpha);
+    }
+
+    public boolean getAnimateChange() {
+        return mAnimateChange;
+    }
+
+    public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+        mAodFrontScrimAlpha = aodFrontScrimAlpha;
+    }
+
+    public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+        mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+    }
+}
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 6775615..dc8100f 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -79,7 +79,6 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
-import android.media.session.MediaSessionManager;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -239,6 +238,7 @@
         .OnChildLocationsChangedListener;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -387,6 +387,7 @@
 
     private VolumeComponent mVolumeComponent;
     private BrightnessMirrorController mBrightnessMirrorController;
+    private boolean mBrightnessMirrorVisible;
     protected FingerprintUnlockController mFingerprintUnlockController;
     private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
@@ -647,6 +648,31 @@
         }
     };
 
+    // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+    // this animation is tied to the scrim for historic reasons.
+    // TODO: notify when keyguard has faded away instead of the scrim.
+    private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+            .Callback() {
+        @Override
+        public void onFinished() {
+            notifyKeyguardState();
+        }
+
+        @Override
+        public void onCancelled() {
+            notifyKeyguardState();
+        }
+
+        private void notifyKeyguardState() {
+            if (mStatusBarKeyguardViewManager == null) {
+                Log.w(TAG, "Tried to notify keyguard visibility when "
+                        + "mStatusBarKeyguardViewManager was null");
+                return;
+            }
+            mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+        }
+    };
+
     private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
@@ -919,7 +945,14 @@
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mGutsManager = new NotificationGutsManager(this, mStackScroller,
-                mCheckSaveListener, mContext);
+                mCheckSaveListener, mContext,
+                key -> {
+                    try {
+                        mBarService.onNotificationSettingsViewed(key);
+                    } catch (RemoteException e) {
+                        // if we're here we're dead
+                    }
+                });
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1039,7 +1072,7 @@
                     if (mStatusBarWindowManager != null) {
                         mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
                     }
-                });
+                }, new DozeParameters(mContext));
         if (mScrimSrcModeEnabled) {
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1075,7 +1108,10 @@
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mIconController);
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
-                    mScrimController);
+                    (visible) -> {
+                        mBrightnessMirrorVisible = visible;
+                        updateScrimController();
+                    });
             fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
                 QS qs = (QS) f;
                 if (qs instanceof QSFragment) {
@@ -1222,13 +1258,13 @@
         reevaluateStyles();
     }
 
-    private void reinflateViews() {
+    private void onThemeChanged() {
         reevaluateStyles();
 
         // Clock and bottom icons
-        mNotificationPanel.onOverlayChanged();
+        mNotificationPanel.onThemeChanged();
         // The status bar on the keyguard is a special layout.
-        if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged();
+        if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged();
         // Recreate Indication controller because internal references changed
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
@@ -1239,11 +1275,8 @@
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
         mKeyguardIndicationController.setDozing(mDozing);
-        if (mBrightnessMirrorController != null) {
-            mBrightnessMirrorController.onOverlayChanged();
-        }
         if (mStatusBarKeyguardViewManager != null) {
-            mStatusBarKeyguardViewManager.onOverlayChanged();
+            mStatusBarKeyguardViewManager.onThemeChanged();
         }
         if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
             ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
@@ -1258,6 +1291,13 @@
         updateEmptyShadeView();
     }
 
+    @Override
+    public void onOverlayChanged() {
+        if (mBrightnessMirrorController != null) {
+            mBrightnessMirrorController.onOverlayChanged();
+        }
+    }
+
     private void updateNotificationsOnDensityOrFontScaleChanged() {
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         for (int i = 0; i < activeNotifications.size(); i++) {
@@ -1447,8 +1487,7 @@
                 mDozeScrimController, keyguardViewMediator,
                 mScrimController, this, UnlockMethodCache.getInstance(mContext));
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
-                getBouncerContainer(), mScrimController,
-                mFingerprintUnlockController);
+                getBouncerContainer(), mFingerprintUnlockController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1470,6 +1509,11 @@
                         }
                     }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
                 }
+                try {
+                    mBarService.onNotificationDirectReplied(entry.key);
+                } catch (RemoteException e) {
+                    // system process is dead if we're here.
+                }
             }
         });
 
@@ -1785,9 +1829,14 @@
         final int id = n.getId();
         final int userId = n.getUserId();
         try {
-            // TODO: record actual dismissal surface
+            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+            if (isHeadsUp(n.getKey())) {
+                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+            } else if (mStackScroller.hasPulsingNotifications()) {
+                dismissalSurface = NotificationStats.DISMISSAL_AOD;
+            }
             mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
-                    NotificationStats.DISMISSAL_OTHER);
+                    dismissalSurface);
             if (FORCE_REMOTE_INPUT_HISTORY
                     && mKeysKeptForRemoteInput.contains(n.getKey())) {
                 mKeysKeptForRemoteInput.remove(n.getKey());
@@ -2620,7 +2669,7 @@
     }
 
     public boolean isPulsing() {
-        return mDozeScrimController.isPulsing();
+        return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
     @Override
@@ -3328,7 +3377,7 @@
         }
 
         if (mScrimController != null) {
-            mScrimController.dump(pw);
+            mScrimController.dump(fd, pw, args);
         }
 
         if (DUMPTRUCK) {
@@ -3393,7 +3442,19 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
-        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+        mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
+          public void setRemoteInputActive(NotificationData.Entry entry,
+                  boolean remoteInputActive) {
+              mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+          }
+          public void lockScrollTo(NotificationData.Entry entry) {
+              mStackScroller.lockScrollTo(entry.row);
+          }
+          public void requestDisallowLongPressAndDismiss() {
+              mStackScroller.requestDisallowLongPress();
+              mStackScroller.requestDisallowDismiss();
+          }
+        });
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -4061,7 +4122,6 @@
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mLaunchTransitionFadingAway = false;
-        mScrimController.forceHideScrims(false /* hide */, false /* animated */);
         updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
@@ -4094,7 +4154,7 @@
             if (beforeFading != null) {
                 beforeFading.run();
             }
-            mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+            updateScrimController();
             updateMediaMetaData(false, true);
             mNotificationPanel.setAlpha(1);
             mStackScroller.setParentNotFullyVisible(true);
@@ -4125,6 +4185,13 @@
                 .setStartDelay(0)
                 .setDuration(FADE_KEYGUARD_DURATION_PULSING)
                 .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        hideKeyguard();
+                        mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                    }
+                })
                 .start();
     }
 
@@ -4132,7 +4199,6 @@
      * Plays the animation when an activity that was occluding Keyguard goes away.
      */
     public void animateKeyguardUnoccluding() {
-        mScrimController.animateKeyguardUnoccluding(500);
         mNotificationPanel.setExpandedFraction(0f);
         animateExpandNotificationsPanel();
     }
@@ -4320,11 +4386,6 @@
                 mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
             }
         }
-        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            mScrimController.setKeyguardShowing(true);
-        } else {
-            mScrimController.setKeyguardShowing(false);
-        }
         mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
         updateTheme();
         updateDozingState();
@@ -4332,6 +4393,7 @@
         updateStackScrollerState(goingToFullShade, fromShadeLocked);
         updateNotifications();
         checkBarModes();
+        updateScrimController();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
         mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                 mUnlockMethodCache.isMethodSecure(),
@@ -4369,7 +4431,7 @@
         if (mContext.getThemeResId() != themeResId) {
             mContext.setTheme(themeResId);
             if (inflated) {
-                reinflateViews();
+                onThemeChanged();
             }
         }
 
@@ -4395,11 +4457,10 @@
         boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
         mNotificationPanel.setDozing(mDozing, animate);
         mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
-        mScrimController.setDozing(mDozing);
+        mDozeScrimController.setDozing(mDozing);
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        mDozeScrimController.setDozing(mDozing, animate);
         updateRowStates();
         Trace.endSection();
     }
@@ -4894,6 +4955,7 @@
         if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
         updateHideIconsForBouncer(true /* animate */);
         recomputeDisableFlags(true /* animate */);
+        updateScrimController();
     }
 
     public void cancelCurrentTouch() {
@@ -4945,12 +5007,10 @@
             mStackScroller.setAnimationsEnabled(true);
             mVisualStabilityManager.setScreenOn(true);
             mNotificationPanel.setTouchDisabled(false);
-
-            maybePrepareWakeUpFromAod();
-
             mDozeServiceHost.stopDozing();
             updateVisibleToUser();
             updateIsKeyguard();
+            updateScrimController();
         }
     };
 
@@ -4960,18 +5020,16 @@
             mFalsingManager.onScreenTurningOn();
             mNotificationPanel.onScreenTurningOn();
 
-            maybePrepareWakeUpFromAod();
-
             if (mLaunchCameraOnScreenTurningOn) {
                 mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
                 mLaunchCameraOnScreenTurningOn = false;
             }
+
+            updateScrimController();
         }
 
         @Override
         public void onScreenTurnedOn() {
-            mScrimController.wakeUpFromAod();
-            mDozeScrimController.onScreenTurnedOn();
         }
 
         @Override
@@ -4989,13 +5047,6 @@
         return mWakefulnessLifecycle.getWakefulness();
     }
 
-    private void maybePrepareWakeUpFromAod() {
-        int wakefulness = mWakefulnessLifecycle.getWakefulness();
-        if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
-            mScrimController.prepareWakeUpFromAod();
-        }
-    }
-
     private void vibrateForCameraGesture() {
         // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
         mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5078,12 +5129,12 @@
             if (!mDeviceInteractive) {
                 // Avoid flickering of the scrim when we instant launch the camera and the bouncer
                 // comes on.
-                mScrimController.dontAnimateBouncerChangesUntilNextFrame();
                 mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
             }
             if (isScreenTurningOnOrOn()) {
                 if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
                 mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+                updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
                 // we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5125,15 +5176,16 @@
     private void updateDozing() {
         Trace.beginSection("StatusBar#updateDozing");
         // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
-        mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+        boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
                 || mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
         // When in wake-and-unlock we may not have received a change to mState
         // but we still should not be dozing, manually set to false.
         if (mFingerprintUnlockController.getMode() ==
                 FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
-            mDozing = false;
+            dozing = false;
         }
+        mDozing = dozing;
         mStatusBarWindowManager.setDozing(mDozing);
         mStatusBarKeyguardViewManager.setDozing(mDozing);
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5143,6 +5195,24 @@
         Trace.endSection();
     }
 
+    public void updateScrimController() {
+        if (mBouncerShowing) {
+            mScrimController.transitionTo(ScrimState.BOUNCER);
+        } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+        } else if (mBrightnessMirrorVisible) {
+            mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+        } else if (isPulsing()) {
+            // Handled in DozeScrimController#setPulsing
+        } else if (mDozing) {
+            mScrimController.transitionTo(ScrimState.AOD);
+        } else if (mIsKeyguard) {
+            mScrimController.transitionTo(ScrimState.KEYGUARD);
+        } else {
+            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+        }
+    }
+
     public boolean isKeyguardShowing() {
         if (mStatusBarKeyguardViewManager == null) {
             Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5202,7 +5272,6 @@
             }
 
             mDozeScrimController.pulse(new PulseCallback() {
-
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
@@ -5287,11 +5356,6 @@
         }
 
         @Override
-        public void abortPulsing() {
-            mDozeScrimController.abortPulsing();
-        }
-
-        @Override
         public void extendPulse() {
             mDozeScrimController.extendPulse();
         }
@@ -5327,7 +5391,7 @@
 
         @Override
         public void setAodDimmingScrim(float scrimOpacity) {
-            mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+            ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
         }
 
         public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09828dc..ef05bbb 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -71,17 +70,14 @@
 
     protected final Context mContext;
     private final StatusBarWindowManager mStatusBarWindowManager;
-    private final boolean mDisplayBlanksAfterDoze;
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
     protected StatusBar mStatusBar;
-    private ScrimController mScrimController;
     private FingerprintUnlockController mFingerprintUnlockController;
 
     private ViewGroup mContainer;
 
-    private boolean mScreenTurnedOn;
     protected KeyguardBouncer mBouncer;
     protected boolean mShowing;
     protected boolean mOccluded;
@@ -95,12 +91,10 @@
     private boolean mLastBouncerDismissible;
     protected boolean mLastRemoteInputActive;
     private boolean mLastDozing;
-    private boolean mLastDeferScrimFadeOut;
     private int mLastFpMode;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
-    private boolean mDeferScrimFadeOut;
 
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
     private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
-        mDisplayBlanksAfterDoze = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_displayBlanksAfterDoze);
     }
 
     public void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
-            ScrimController scrimController,
             FingerprintUnlockController fingerprintUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry) {
         mStatusBar = statusBar;
         mContainer = container;
-        mScrimController = scrimController;
         mFingerprintUnlockController = fingerprintUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@
     public void show(Bundle options) {
         mShowing = true;
         mStatusBarWindowManager.setKeyguardShowing(true);
-        mScrimController.abortKeyguardFadingOut();
         reset(true /* hideBouncerWhenShowing */);
     }
 
@@ -253,15 +242,7 @@
     }
 
     public void onScreenTurnedOn() {
-        Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
-        mScreenTurnedOn = true;
-        if (mDeferScrimFadeOut) {
-            mDeferScrimFadeOut = false;
-            animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
-                    true /* skipFirstFrame */);
-            updateStates();
-        }
-        Trace.endSection();
+        // TODO: remove
     }
 
     @Override
@@ -285,7 +266,7 @@
     }
 
     public void onScreenTurnedOff() {
-        mScreenTurnedOn = false;
+        // TODO: remove
     }
 
     public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
                     hideBouncer(true /* destroyView */);
                     updateStates();
-                    mScrimController.animateKeyguardFadingOut(
-                            StatusBar.FADE_KEYGUARD_START_DELAY,
-                            StatusBar.FADE_KEYGUARD_DURATION, null,
-                            false /* skipFirstFrame */);
                 }
             }, new Runnable() {
                 @Override
@@ -400,36 +377,16 @@
             mFingerprintUnlockController.startKeyguardFadingAway();
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
-                mStatusBarWindowManager.setKeyguardFadingAway(true);
                 mStatusBar.fadeKeyguardWhilePulsing();
-                animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                        mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+                wakeAndUnlockDejank();
             } else {
                 mFingerprintUnlockController.startKeyguardFadingAway();
                 mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
                 boolean staying = mStatusBar.hideKeyguard();
                 if (!staying) {
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
-                    if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
-                        boolean turnedOnSinceAuth =
-                                mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
-                        if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
-                            // Not ready to animate yet; either because the screen is not on yet,
-                            // or it is on but will turn off before waking out of doze.
-                            mDeferScrimFadeOut = true;
-                        } else {
-
-                            // Screen is already on, don't defer with fading out.
-                            animateScrimControllerKeyguardFadingOut(0,
-                                    WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
-                                    true /* skipFirstFrame */);
-                        }
-                    } else {
-                        animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                                false /* skipFirstFrame */);
-                    }
+                    wakeAndUnlockDejank();
                 } else {
-                    mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
                     mStatusBar.finishKeyguardFadingAway();
                     mFingerprintUnlockController.finishKeyguardFadingAway();
                 }
@@ -444,35 +401,22 @@
         hideBouncer(true /* destroyView */);
     }
 
-    public void onOverlayChanged() {
+    public void onThemeChanged() {
         hideBouncer(true /* destroyView */);
         mBouncer.prepare();
     }
 
-    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
-            boolean skipFirstFrame) {
-        animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
-                skipFirstFrame);
+    public void onKeyguardFadedAway() {
+        mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+                100);
+        mStatusBar.finishKeyguardFadingAway();
+        mFingerprintUnlockController.finishKeyguardFadingAway();
+        WindowManagerGlobal.getInstance().trimMemory(
+                ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
     }
 
-    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
-            final Runnable endRunnable, boolean skipFirstFrame) {
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
-        mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
-            @Override
-            public void run() {
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-                mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
-                        100);
-                mStatusBar.finishKeyguardFadingAway();
-                mFingerprintUnlockController.finishKeyguardFadingAway();
-                WindowManagerGlobal.getInstance().trimMemory(
-                        ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
-            }
-        }, skipFirstFrame);
+    private void wakeAndUnlockDejank() {
         if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
                 && LatencyTracker.isEnabled(mContext)) {
             DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@
         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
             mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
             mStatusBar.setBouncerShowing(bouncerShowing);
-            mScrimController.setBouncerShowing(bouncerShowing);
         }
 
         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@
         mLastBouncerDismissible = bouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
         mLastDozing = mDozing;
-        mLastDeferScrimFadeOut = mDeferScrimFadeOut;
         mLastFpMode = mFingerprintUnlockController.getMode();
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
@@ -624,7 +566,7 @@
         boolean keyguardShowing = mShowing && !mOccluded;
         boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
-                || mRemoteInputActive) && !mDeferScrimFadeOut;
+                || mRemoteInputActive);
     }
 
     /**
@@ -634,7 +576,7 @@
         boolean keyguardShowing = mLastShowing && !mLastOccluded;
         boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
-                || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+                || mLastRemoteInputActive);
     }
 
     public boolean shouldDismissOnMenuPressed() {
diff --git a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 42ce4c5..a011952 100644
--- a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
 import android.util.ArraySet;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
@@ -25,55 +27,52 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
+import java.util.function.Consumer;
+
 /**
  * Controls showing and hiding of the brightness mirror.
  */
 public class BrightnessMirrorController
         implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
 
-    private final NotificationStackScrollLayout mStackScroller;
-    public long TRANSITION_DURATION_OUT = 150;
-    public long TRANSITION_DURATION_IN = 200;
+    private final static long TRANSITION_DURATION_OUT = 150;
+    private final static long TRANSITION_DURATION_IN = 200;
 
     private final StatusBarWindowView mStatusBarWindow;
-    private final ScrimController mScrimController;
+    private final NotificationStackScrollLayout mStackScroller;
+    private final Consumer<Boolean> mVisibilityCallback;
     private final View mNotificationPanel;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
     private final int[] mInt2Cache = new int[2];
     private View mBrightnessMirror;
 
     public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
-            ScrimController scrimController) {
+            @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
         mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
-        mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById(
-                R.id.notification_stack_scroller);
-        mScrimController = scrimController;
+        mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
+        mVisibilityCallback = visibilityCallback;
     }
 
     public void showMirror() {
         mBrightnessMirror.setVisibility(View.VISIBLE);
         mStackScroller.setFadingOut(true);
-        mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+        mVisibilityCallback.accept(true);
         outAnimation(mNotificationPanel.animate())
                 .withLayer();
     }
 
     public void hideMirror() {
-        mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+        mVisibilityCallback.accept(false);
         inAnimation(mNotificationPanel.animate())
                 .withLayer()
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mBrightnessMirror.setVisibility(View.INVISIBLE);
-                        mStackScroller.setFadingOut(false);
-                    }
+                .withEndAction(() -> {
+                    mBrightnessMirror.setVisibility(View.INVISIBLE);
+                    mStackScroller.setFadingOut(false);
                 });
     }
 
@@ -128,9 +127,11 @@
     }
 
     private void reinflate() {
+        ContextThemeWrapper qsThemeContext =
+                new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme);
         int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
         mStatusBarWindow.removeView(mBrightnessMirror);
-        mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate(
+        mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate(
                 R.layout.brightness_mirror, mStatusBarWindow, false);
         mStatusBarWindow.addView(mBrightnessMirror, index);
 
diff --git a/com/android/systemui/statusbar/policy/RemoteInputView.java b/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de4..4fc5044 100644
--- a/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,11 +53,9 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
@@ -82,8 +80,6 @@
 
     private NotificationData.Entry mEntry;
 
-    private ScrollContainer mScrollContainer;
-    private View mScrollContainerChild;
     private boolean mRemoved;
 
     private int mRevealCx;
@@ -347,41 +343,16 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            findScrollContainer();
-            if (mScrollContainer != null) {
-                mScrollContainer.requestDisallowLongPress();
-                mScrollContainer.requestDisallowDismiss();
-            }
+            mController.requestDisallowLongPressAndDismiss();
         }
         return super.onInterceptTouchEvent(ev);
     }
 
     public boolean requestScrollTo() {
-        findScrollContainer();
-        mScrollContainer.lockScrollTo(mScrollContainerChild);
+        mController.lockScrollTo(mEntry);
         return true;
     }
 
-    private void findScrollContainer() {
-        if (mScrollContainer == null) {
-            mScrollContainerChild = null;
-            ViewParent p = this;
-            while (p != null) {
-                if (mScrollContainerChild == null && p instanceof ExpandableView) {
-                    mScrollContainerChild = (View) p;
-                }
-                if (p.getParent() instanceof ScrollContainer) {
-                    mScrollContainer = (ScrollContainer) p.getParent();
-                    if (mScrollContainerChild == null) {
-                        mScrollContainerChild = (View) p;
-                    }
-                    break;
-                }
-                p = p.getParent();
-            }
-        }
-    }
-
     public boolean isActive() {
         return mEditText.isFocused() && mEditText.isEnabled();
     }
diff --git a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf..f5ae88b 100644
--- a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@
                     avatar = new UserIconDrawable(avatarSize)
                             .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
                 } else {
-                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+                    avatar = UserIcons.getDefaultUserIcon(
+                            context.getResources(),
+                            isGuest? UserHandle.USER_NULL : userId,
                             lightIcon);
                 }
 
diff --git a/com/android/systemui/statusbar/policy/UserSwitcherController.java b/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a..7006d38 100644
--- a/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@
             if (item.isAddUser) {
                 return context.getDrawable(R.drawable.ic_add_circle_qs);
             }
-            Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+            Drawable icon = UserIcons.getDefaultUserIcon(
+                    context.getResources(), item.resolveId(), /* light= */ false);
             if (item.isGuest) {
                 icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
                         Mode.SRC_IN);
@@ -910,6 +911,7 @@
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(R.string.guest_exit_guest_dialog_remove), this);
+            SystemUIDialog.setWindowOnTop(this);
             setCanceledOnTouchOutside(false);
             mGuestId = guestId;
             mTargetId = targetId;
@@ -937,6 +939,7 @@
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(android.R.string.ok), this);
+            SystemUIDialog.setWindowOnTop(this);
         }
 
         @Override
@@ -957,7 +960,7 @@
                 }
                 int id = user.id;
                 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
-                        id, /* light= */ false));
+                        mContext.getResources(), id, /* light= */ false));
                 mUserManager.setUserIcon(id, icon);
                 switchToUserId(id);
             }
diff --git a/com/android/systemui/statusbar/stack/AnimationProperties.java b/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d..2f6e658 100644
--- a/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@
      * @return an animation filter for this animation.
      */
     public AnimationFilter getAnimationFilter() {
-        return new AnimationFilter();
+        return new AnimationFilter() {
+            @Override
+            public boolean shouldAnimateProperty(Property property) {
+                return true;
+            }
+        };
     }
 
     /**
diff --git a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104..c0241e3 100644
--- a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -1260,4 +1260,17 @@
     public boolean isUserLocked() {
         return mUserLocked;
     }
+
+    public void setCurrentBottomRoundness(float currentBottomRoundness) {
+        boolean last = true;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            ExpandableNotificationRow child = mChildren.get(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+            child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+            last = false;
+        }
+    }
 }
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index efe049a..ebebfac 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -108,8 +112,7 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
-        VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -121,12 +124,23 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
+    private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+            "sidePaddings",
+            NotificationStackScrollLayout::setCurrentSidePadding,
+            NotificationStackScrollLayout::getCurrentSidePadding,
+            R.id.side_padding_animator_tag,
+            R.id.side_padding_animator_end_tag,
+            R.id.side_padding_animator_start_tag);
+    private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private boolean mSwipingInProgress;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
+    private final Path mBackgroundPath = new Path();
+    private final float[] mBackgroundRadii = new float[8];
     private final boolean mShouldDrawNotificationBackground;
 
     private float mExpandedHeight;
@@ -157,6 +171,7 @@
     private int mTopPadding;
     private int mBottomMargin;
     private int mBottomInset = 0;
+    private float mCurrentSidePadding;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -383,6 +398,9 @@
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mAnimateScroll = this::animateScroll;
+    private int mCornerRadius;
+    private int mLockscreenSidePaddings;
+    private int mSidePaddings;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -419,6 +437,8 @@
                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
 
         updateWillNotDraw();
+        mBackgroundPaint.setAntiAlias(true);
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
         if (DEBUG) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
@@ -466,8 +486,7 @@
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
                 && mCurrentBounds.top < mCurrentBounds.bottom) {
-            canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
-                    mBackgroundPaint);
+            canvas.drawPath(mBackgroundPath, mBackgroundPaint);
         }
 
         if (DEBUG) {
@@ -520,8 +539,12 @@
                 R.dimen.min_top_overscroll_to_qs);
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
         mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+        mLockscreenSidePaddings = res.getDimensionPixelSize(
+                R.dimen.notification_lockscreen_side_paddings);
         mMinInteractionHeight = res.getDimensionPixelSize(
                 R.dimen.notification_min_interaction_height);
+        mCornerRadius = res.getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
     }
 
     public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -1195,7 +1218,6 @@
         mScrollingEnabled = enable;
     }
 
-    @Override
     public void lockScrollTo(View v) {
         if (mForcedScroll == v) {
             return;
@@ -1204,7 +1226,6 @@
         scrollTo(v);
     }
 
-    @Override
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
         int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -2221,9 +2242,31 @@
         mScrimController.setExcludedBackgroundArea(
                 mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
                         : mCurrentBounds);
+        updateBackgroundPath();
         invalidate();
     }
 
+    private void updateBackgroundPath() {
+        mBackgroundPath.reset();
+        float topRoundness = 0;
+        if (mFirstVisibleBackgroundChild != null) {
+            topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+        }
+        topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+        float bottomRoundNess = mCornerRadius;
+        mBackgroundRadii[0] = topRoundness;
+        mBackgroundRadii[1] = topRoundness;
+        mBackgroundRadii[2] = topRoundness;
+        mBackgroundRadii[3] = topRoundness;
+        mBackgroundRadii[4] = bottomRoundNess;
+        mBackgroundRadii[5] = bottomRoundNess;
+        mBackgroundRadii[6] = bottomRoundNess;
+        mBackgroundRadii[7] = bottomRoundNess;
+        mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+                getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+                Path.Direction.CCW);
+    }
+
     /**
      * Update the background bounds to the new desired bounds
      */
@@ -2236,6 +2279,8 @@
             mBackgroundBounds.left = mTempInt2[0];
             mBackgroundBounds.right = mTempInt2[0] + getWidth();
         }
+        mBackgroundBounds.left += mCurrentSidePadding;
+        mBackgroundBounds.right -= mCurrentSidePadding;
         if (!mIsExpanded) {
             mBackgroundBounds.top = 0;
             mBackgroundBounds.bottom = 0;
@@ -2820,16 +2865,45 @@
     private void updateFirstAndLastBackgroundViews() {
         ActivatableNotificationView firstChild = getFirstChildWithBackground();
         ActivatableNotificationView lastChild = getLastChildWithBackground();
+        boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+        boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
         if (mAnimationsEnabled && mIsExpanded) {
-            mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
-            mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+            mAnimateNextBackgroundTop = firstChanged;
+            mAnimateNextBackgroundBottom = lastChanged;
         } else {
             mAnimateNextBackgroundTop = false;
             mAnimateNextBackgroundBottom = false;
         }
+        if (firstChanged && mFirstVisibleBackgroundChild != null
+                && !mFirstVisibleBackgroundChild.isRemoved()) {
+            mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+                    mFirstVisibleBackgroundChild.isShown());
+        }
+        if (lastChanged && mLastVisibleBackgroundChild != null
+                && !mLastVisibleBackgroundChild.isRemoved()) {
+            mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+                    mLastVisibleBackgroundChild.isShown());
+        }
         mFirstVisibleBackgroundChild = firstChild;
         mLastVisibleBackgroundChild = lastChild;
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
+        applyRoundedNess();
+    }
+
+    private void applyRoundedNess() {
+        if (mFirstVisibleBackgroundChild != null) {
+            mFirstVisibleBackgroundChild.setTopRoundness(
+                    mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f,
+                    mFirstVisibleBackgroundChild.isShown()
+                            && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+        }
+        if (mLastVisibleBackgroundChild != null) {
+            mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+                    mLastVisibleBackgroundChild.isShown()
+                            && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+        }
+        updateBackgroundPath();
+        invalidate();
     }
 
     private void onViewAddedInternal(View child) {
@@ -2838,6 +2912,7 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
+        updateCurrentSidePaddings(child);
     }
 
     private void updateHideSensitiveForChild(View child) {
@@ -3321,12 +3396,10 @@
         }
     }
 
-    @Override
     public void requestDisallowLongPress() {
         cancelLongPress();
     }
 
-    @Override
     public void requestDisallowDismiss() {
         mDisallowDismissInThisMotion = true;
     }
@@ -4285,6 +4358,43 @@
     public void setStatusBarState(int statusBarState) {
         mStatusBarState = statusBarState;
         mAmbientState.setStatusBarState(statusBarState);
+        applyRoundedNess();
+        updateSidePaddings();
+    }
+
+    private void updateSidePaddings() {
+        int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+        if (sidePaddings != mSidePaddings) {
+            boolean animate = isShown();
+            mSidePaddings = sidePaddings;
+            PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+                    SIDE_PADDING_PROPERTIES, animate);
+        }
+    }
+
+    protected void setCurrentSidePadding(float sidePadding) {
+        mCurrentSidePadding = sidePadding;
+        updateBackground();
+        applySidePaddingsToChildren();
+    }
+
+    private void applySidePaddingsToChildren() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            updateCurrentSidePaddings(view);
+        }
+    }
+
+    private void updateCurrentSidePaddings(View view) {
+        if (!(view instanceof ExpandableOutlineView)) {
+            return;
+        }
+        ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+        outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+    }
+
+    protected float getCurrentSidePadding() {
+        return mCurrentSidePadding;
     }
 
     public void setExpandingVelocity(float expandingVelocity) {
diff --git a/com/android/systemui/statusbar/stack/ScrollContainer.java b/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce..0000000
--- a/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
-    /**
-     * Request that the view does not perform long press for the current touch.
-     */
-    void requestDisallowLongPress();
-
-    /**
-     * Request that the view is made visible by scrolling to it.
-     * Return true if it scrolls.
-     */
-    boolean scrollTo(View v);
-
-    /**
-     * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
-     * scrolls, or {@param v} loses focus or is detached.
-     */
-    void lockScrollTo(View v);
-
-    /**
-     * Request that the view does not dismiss for the current touch.
-     */
-    void requestDisallowDismiss();
-}
diff --git a/com/android/systemui/statusbar/stack/ViewState.java b/com/android/systemui/statusbar/stack/ViewState.java
index 27b730c..682b849 100644
--- a/com/android/systemui/statusbar/stack/ViewState.java
+++ b/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
-import android.app.Notification;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -64,8 +63,8 @@
     private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
     private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
 
-    private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
-            = new PropertyAnimator.AnimatableProperty() {
+    private static final AnimatableProperty SCALE_X_PROPERTY
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
@@ -88,8 +87,8 @@
         }
     };
 
-    private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
-            = new PropertyAnimator.AnimatableProperty() {
+    private static final AnimatableProperty SCALE_Y_PROPERTY
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
@@ -251,7 +250,7 @@
         return getChildTag(view, tag) != null;
     }
 
-    public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+    public static boolean isAnimating(View view, AnimatableProperty property) {
         return getChildTag(view, property.getAnimatorTag()) != null;
     }
 
@@ -403,7 +402,7 @@
         startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
     }
 
-    private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+    private void updateAnimation(View view, AnimatableProperty property,
             float endValue) {
         PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
     }
diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java
index 87d11b2..4606aee 100644
--- a/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/com/android/systemui/usb/UsbPermissionActivity.java
@@ -235,7 +235,7 @@
                 intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
                 if (mPermissionGranted) {
                     service.grantDevicePermission(mDevice, mUid);
-                    if (mAlwaysUse.isChecked()) {
+                    if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
                         final int userId = UserHandle.getUserId(mUid);
                         service.setDevicePackage(mDevice, mPackageName, userId);
                     }
@@ -245,7 +245,7 @@
                 intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
                 if (mPermissionGranted) {
                     service.grantAccessoryPermission(mAccessory, mUid);
-                    if (mAlwaysUse.isChecked()) {
+                    if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
                         final int userId = UserHandle.getUserId(mUid);
                         service.setAccessoryPackage(mAccessory, mPackageName, userId);
                     }
diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java
index 4b8f581..383d327 100644
--- a/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/com/android/systemui/volume/VolumeDialogImpl.java
@@ -207,6 +207,7 @@
         } else {
             addExistingRows();
         }
+        updateRowsH(getActiveRow());
     }
 
     private ColorStateList loadColorStateList(int colorResId) {
@@ -440,7 +441,8 @@
                 .withEndAction(() -> mDialog.dismiss())
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .start();
-        if (mAccessibilityMgr.isEnabled()) {
+        if (mAccessibilityMgr.isObservedEventType(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
             event.setPackageName(mContext.getPackageName());