Disabling timer

- Disabling the timer by default
- Adding flag to enable/disable paging (disabled by default).  When 
  disabled, the second tap will just launch the next task.
- Updating the initial state to put tasks at specific points on the 
  curve.  This allows us to allocate specific space for the front most
  tasks, as well as the tasks behind it.
- Removing hardware layer config (no devices targeting N depend on it)
- Fixing issue with the header not having the final dim applied when 
  transitioning into recents.
- Fixing issue where the stack range would not be reloaded after docking
  or undocking a task.

Bug: 27409035

Change-Id: I13a03fba2b1a770f15d0033d43ef4c28415041c9
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8a7f90b..12c3a5d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -292,6 +292,9 @@
     <!-- The amount to allow the stack to overscroll. -->
     <dimen name="recents_stack_overscroll">24dp</dimen>
 
+    <!-- The size of the initial peek area at the top of the stack (below the status bar). -->
+    <dimen name="recents_initial_top_peek_size">8dp</dimen>
+
     <!-- The size of the peek area at the top of the stack (below the status bar). -->
     <dimen name="recents_layout_focused_top_peek_size">@dimen/recents_history_button_height</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d7c12ba..a2934d74 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -43,6 +43,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
@@ -107,7 +108,7 @@
     private RecentsAppWidgetHostView mSearchWidgetHostView;
 
     // Runnables to finish the Recents activity
-    private FinishRecentsRunnable mFinishLaunchHomeRunnable;
+    private Intent mHomeIntent;
 
     // The trigger to automatically launch the current task
     private int mFocusTimerDuration;
@@ -119,7 +120,7 @@
      * last activity launch state. Generally we always launch home when we exit Recents rather than
      * just finishing the activity since we don't know what is behind Recents in the task stack.
      */
-    class FinishRecentsRunnable implements Runnable {
+    class LaunchHomeRunnable implements Runnable {
 
         Intent mLaunchIntent;
         ActivityOptions mOpts;
@@ -127,7 +128,7 @@
         /**
          * Creates a finish runnable that starts the specified intent.
          */
-        public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
+        public LaunchHomeRunnable(Intent launchIntent, ActivityOptions opts) {
             mLaunchIntent = launchIntent;
             mOpts = opts;
         }
@@ -215,7 +216,7 @@
             MetricsLogger.count(this, "overview_trigger_nav_btn", 1);
         }
         // Keep track of whether we launched from an app or from home
-        if (launchState.launchedFromAppWithThumbnail) {
+        if (launchState.launchedFromApp) {
             MetricsLogger.count(this, "overview_source_app", 1);
             // If from an app, track the stack index of the app in the stack (for affiliated tasks)
             MetricsLogger.histogram(this, "overview_source_app_index", launchTaskIndexInStack);
@@ -294,12 +295,8 @@
     void dismissRecentsToHome(boolean animateTaskViews, ActivityOptions overrideAnimation) {
         DismissRecentsToHomeAnimationStarted dismissEvent =
                 new DismissRecentsToHomeAnimationStarted(animateTaskViews);
-        if (overrideAnimation != null) {
-            dismissEvent.addPostAnimationCallback(new FinishRecentsRunnable(
-                    mFinishLaunchHomeRunnable.mLaunchIntent, overrideAnimation));
-        } else {
-            dismissEvent.addPostAnimationCallback(mFinishLaunchHomeRunnable);
-        }
+        dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
+                overrideAnimation));
         dismissEvent.addPostAnimationCallback(new Runnable() {
             @Override
             public void run() {
@@ -365,11 +362,10 @@
         });
 
         // Create the home intent runnable
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-        homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+        mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
+        mHomeIntent.addCategory(Intent.CATEGORY_HOME);
+        mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent, null);
 
         // Bind the search app widget when we first start up
         if (RecentsDebugFlags.Static.EnableSearchBar) {
@@ -404,7 +400,7 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
-                !launchState.launchedFromAppWithThumbnail;
+                !launchState.launchedFromApp;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
             EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
         }
@@ -528,6 +524,7 @@
     @Override
     public void onMultiWindowChanged(boolean inMultiWindow) {
         super.onMultiWindowChanged(inMultiWindow);
+        EventBus.getDefault().send(new ConfigurationChangedEvent());
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
         launchOpts.loadIcons = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index aa1437b..ec4820a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -28,7 +28,8 @@
 public class RecentsActivityLaunchState {
 
     public boolean launchedWithAltTab;
-    public boolean launchedFromAppWithThumbnail;
+    public boolean launchedFromApp;
+    public boolean launchedFromAppDocked;
     public boolean launchedFromHome;
     public boolean launchedFromSearchHome;
     public boolean launchedReuseTaskStackViews;
@@ -42,7 +43,8 @@
     public void reset() {
         launchedFromHome = false;
         launchedFromSearchHome = false;
-        launchedFromAppWithThumbnail = false;
+        launchedFromApp = false;
+        launchedFromAppDocked = false;
         launchedToTaskId = -1;
         launchedWithAltTab = false;
         launchedHasConfigurationChanged = false;
@@ -67,7 +69,7 @@
     public int getInitialFocusTaskIndex(int numTasks) {
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        if (launchedFromAppWithThumbnail) {
+        if (launchedFromApp) {
             if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
                 // If fast toggling, focus the front most task so that the next tap will focus the
                 // N-1 task
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 9e43bb4..eec0411 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -69,7 +69,6 @@
     public final int smallestWidth;
 
     /** Misc **/
-    public boolean useHardwareLayers;
     public boolean fakeShadows;
     public int svelteLevel;
     public int searchBarSpaceHeightPx;
@@ -80,7 +79,6 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         Context appContext = context.getApplicationContext();
         Resources res = appContext.getResources();
-        useHardwareLayers = res.getBoolean(R.bool.config_recents_use_hardware_layers);
         fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
         svelteLevel = res.getInteger(R.integer.recents_svelte_level);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index cd64323..6feda81 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -39,8 +39,8 @@
         public static final boolean EnableAffiliatedTaskGroups = true;
         // Enables the history
         public static final boolean EnableHistory = false;
-        // Overrides the Tuner flags and enables the fast toggle and timeout
-        public static final boolean EnableFastToggleTimeoutOverride = true;
+        // Overrides the Tuner flags and enables the timeout
+        private static final boolean EnableFastToggleTimeout = false;
 
         // Enables us to create mock recents tasks
         public static final boolean EnableMockTasks = false;
@@ -54,9 +54,9 @@
         public static final int MockTaskGroupsTaskCount = 12;
     }
 
-    private static final String KEY_DISABLE_FAST_TOGGLE = "overview_disable_fast_toggle_via_button";
+    private static final String KEY_ENABLE_PAGING = "overview_enable_paging";
 
-    private boolean mDisableFastToggleRecents;
+    private boolean mEnablePaging;
 
     /**
      * We read the prefs once when we start the activity, then update them as the tuner changes
@@ -65,31 +65,32 @@
     public RecentsDebugFlags(Context context) {
         // Register all our flags, this will also call onTuningChanged() for each key, which will
         // initialize the current state of each flag
-        TunerService.get(context).addTunable(this, KEY_DISABLE_FAST_TOGGLE);
+        TunerService.get(context).addTunable(this, KEY_ENABLE_PAGING);
     }
 
     /**
      * @return whether we are enabling fast toggling.
      */
     public boolean isFastToggleRecentsEnabled() {
-        // These checks EnableFastToggleTimeoutOverride
         SystemServicesProxy ssp = Recents.getSystemServices();
-        if (mDisableFastToggleRecents || ssp.hasFreeformWorkspaceSupport() || ssp.hasDockedTask()
-                || ssp.isTouchExplorationEnabled()) {
+        if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) {
             return false;
         }
-        if (Static.EnableFastToggleTimeoutOverride) {
-            return true;
-        }
-        return true;
+        return Static.EnableFastToggleTimeout;
+    }
+
+    /**
+     * @return whether we are enabling paging.
+     */
+    public boolean isPagingEnabled() {
+        return mEnablePaging;
     }
 
     @Override
     public void onTuningChanged(String key, String newValue) {
         switch (key) {
-            case KEY_DISABLE_FAST_TOGGLE:
-                mDisableFastToggleRecents = (newValue != null) &&
-                        (Integer.parseInt(newValue) != 0);
+            case KEY_ENABLE_PAGING:
+                mEnablePaging = (newValue != null) && (Integer.parseInt(newValue) != 0);
                 break;
         }
         EventBus.getDefault().send(new DebugFlagsChangedEvent());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 86b03c8..58219bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.recents;
 
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ITaskStackListener;
@@ -66,6 +68,7 @@
 import com.android.systemui.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewScroller;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.statusbar.BaseStatusBar;
@@ -74,8 +77,6 @@
 
 import java.util.ArrayList;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-
 /**
  * An implementation of the Recents component for the current user.  For secondary users, this can
  * be called remotely from the system user.
@@ -98,6 +99,7 @@
 
     //Used to store tv or non-tv activty for use in creating intents.
     private final String mRecentsIntentActivityName;
+
     /**
      * An implementation of ITaskStackListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
@@ -276,28 +278,24 @@
         mTriggeredFromAltTab = triggeredFromAltTab;
         mDraggingInRecents = draggingInRecents;
         mLaunchedWhileDocking = launchedWhileDockingTask;
-        if (mFastAltTabTrigger.hasTriggered()) {
-            // We are calling this from the doze trigger, so just fall through to show Recents
-            mFastAltTabTrigger.resetTrigger();
+        if (mFastAltTabTrigger.isAsleep()) {
+            // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
+            mFastAltTabTrigger.stopDozing();
         } else if (mFastAltTabTrigger.isDozing()) {
-            // We are dozing but haven't yet triggered, ignore this if this is not another alt-tab,
-            // otherwise, this is an additional tab (alt-tab*), which means that we should trigger
-            // immediately (fall through and disable the pending trigger)
-            // TODO: This is tricky, we need to handle the tab key, but Recents has not yet started
-            //       so we may actually additional signal to handle multiple quick tab cases.  The
-            //       severity of this is inversely proportional to the FAST_ALT_TAB_DELAY_MS
-            //       duration though
+            // Fast alt-tab duration has not elapsed.  If this is triggered by a different
+            // showRecents() call, then ignore that call for now.
+            // TODO: We can not handle quick tabs that happen between the initial showRecents() call
+            //       that started the activity and the activity starting up.  The severity of this
+            //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
             if (!triggeredFromAltTab) {
                 return;
             }
             mFastAltTabTrigger.stopDozing();
-        } else {
-            // Otherwise, the doze trigger is not running, and if this is an alt tab, we should
-            // start the trigger and then wait for the hide (or for it to elapse)
-            if (triggeredFromAltTab) {
-                mFastAltTabTrigger.startDozing();
-                return;
-            }
+        } else if (triggeredFromAltTab) {
+            // The fast alt-tab detector is not yet running, so start the trigger and wait for the
+            // hideRecents() call, or for the fast alt-tab duration to elapse
+            mFastAltTabTrigger.startDozing();
+            return;
         }
 
         try {
@@ -321,7 +319,6 @@
 
             // Cancel the fast alt-tab trigger
             mFastAltTabTrigger.stopDozing();
-            mFastAltTabTrigger.resetTrigger();
             return;
         }
 
@@ -348,12 +345,14 @@
             long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
 
             if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
+                RecentsDebugFlags debugFlags = Recents.getDebugFlags();
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
                 if (!launchState.launchedWithAltTab) {
                     // If the user taps quickly
-                    if (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
-                            elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
+                    if (!debugFlags.isPagingEnabled() ||
+                            (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
+                                    elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
                         // Launch the next focused task
                         EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
                     } else {
@@ -574,7 +573,7 @@
                     false /* triggeredFromAltTab */,
                     dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
                     false /* animate */,
-                    true /* reloadTasks*/);
+                    true /* launchedWhileDockingTask*/);
         }
     }
 
@@ -707,8 +706,7 @@
         // Update the destination rect
         mDummyStackView.updateLayoutForStack(stack);
         final Task toTask = new Task();
-        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                toTask);
+        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
         ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
             @Override
             public void run() {
@@ -754,17 +752,20 @@
      * Creates the activity options for an app->recents transition.
      */
     private ActivityOptions getThumbnailTransitionActivityOptions(
-            ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
+            ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) {
         if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
             ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
-            stackView.getScroller().setStackScrollToInitialState();
-            ArrayList<Task> tasks = stack.getStackTasks();
+            ArrayList<Task> tasks = stackView.getStack().getStackTasks();
+            TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
+            TaskStackViewScroller stackScroller = stackView.getScroller();
+
+            stackView.updateToInitialState();
+
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 Task task = tasks.get(i);
                 if (task.isFreeformTask()) {
-                    mTmpTransform = stackView.getStackAlgorithm()
-                            .getStackTransformScreenCoordinates(task,
-                                    stackView.getScroller().getStackScroll(), mTmpTransform, null);
+                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
+                                    stackScroller.getStackScroll(), mTmpTransform, null);
                     Rect toTaskRect = new Rect();
                     mTmpTransform.rect.round(toTaskRect);
                     Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
@@ -778,8 +779,7 @@
         } else {
             // Update the destination rect
             Task toTask = new Task();
-            TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
-                    toTask);
+            TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
             RectF toTaskRect = toTransform.rect;
             Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
             if (thumbnail != null) {
@@ -811,9 +811,10 @@
     /**
      * Returns the transition rect for the given task id.
      */
-    private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
-            TaskStackView stackView, Task runningTaskOut) {
+    private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
+            Task runningTaskOut) {
         // Find the running task in the TaskStack
+        TaskStack stack = stackView.getStack();
         Task launchTask = stack.getLaunchTarget();
         if (launchTask != null) {
             runningTaskOut.copyFrom(launchTask);
@@ -824,7 +825,7 @@
         }
 
         // Get the transform for the running task
-        stackView.getScroller().setStackScrollToInitialState();
+        stackView.updateToInitialState();
         mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
                 stackView.getScroller().getStackScroll(), mTmpTransform, null);
         return mTmpTransform;
@@ -852,6 +853,7 @@
                     c.scale(toTransform.scale, toTransform.scale);
                     mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
                             disabledInSafeMode);
+                    mHeaderBar.setDimAlpha(toTransform.dimAlpha);
                     mHeaderBar.draw(c);
                     c.setBitmap(null);
                 }
@@ -900,8 +902,7 @@
 
         if (useThumbnailTransition) {
             // Try starting with a thumbnail transition
-            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
-                    mDummyStackView);
+            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
             if (opts != null) {
                 startRecentsActivity(topTask, opts, false /* fromHome */,
                         false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
@@ -948,14 +949,15 @@
      * Starts the recents activity.
      */
     private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
-              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
-              TaskStackLayoutAlgorithm.VisibilityReport vr) {
+                ActivityOptions opts, boolean fromHome, boolean fromSearchHome,
+                boolean fromThumbnail, TaskStackLayoutAlgorithm.VisibilityReport vr) {
         // Update the configuration based on the launch options
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         launchState.launchedFromHome = fromSearchHome || fromHome;
         launchState.launchedFromSearchHome = fromSearchHome;
-        launchState.launchedFromAppWithThumbnail = fromThumbnail;
+        launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking;
+        launchState.launchedFromAppDocked = mLaunchedWhileDocking;
         launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
         launchState.launchedWithAltTab = mTriggeredFromAltTab;
         launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
new file mode 100644
index 0000000..0ad4681
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ConfigurationChangedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * 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.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the Recents activity configuration has changed.
+ */
+public class ConfigurationChangedEvent extends EventBus.AnimatedEvent {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
index 95aa10f..574ea03 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/DozeTrigger.java
@@ -30,7 +30,7 @@
     @ViewDebug.ExportedProperty(category="recents")
     boolean mIsDozing;
     @ViewDebug.ExportedProperty(category="recents")
-    boolean mHasTriggered;
+    boolean mIsAsleep;
     @ViewDebug.ExportedProperty(category="recents")
     int mDozeDurationMilliseconds;
     Runnable mOnSleepRunnable;
@@ -40,7 +40,7 @@
         @Override
         public void run() {
             mIsDozing = false;
-            mHasTriggered = true;
+            mIsAsleep = true;
             mOnSleepRunnable.run();
         }
     };
@@ -56,7 +56,7 @@
      */
     public void startDozing() {
         forcePoke();
-        mHasTriggered = false;
+        mIsAsleep = false;
     }
 
     /**
@@ -65,6 +65,7 @@
     public void stopDozing() {
         mHandler.removeCallbacks(mDozeRunnable);
         mIsDozing = false;
+        mIsAsleep = false;
     }
 
     /**
@@ -99,12 +100,7 @@
     }
 
     /** Returns whether the trigger has fired at least once. */
-    public boolean hasTriggered() {
-        return mHasTriggered;
-    }
-
-    /** Resets the doze trigger state. */
-    public void resetTrigger() {
-        mHasTriggered = false;
+    public boolean isAsleep() {
+        return mIsAsleep;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index 0c48cf7..4ba9efa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -40,7 +40,6 @@
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.TaskStackUpdatedEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
@@ -284,7 +283,7 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         boolean wasLaunchedByAm = !launchState.launchedFromHome &&
-                !launchState.launchedFromAppWithThumbnail;
+                !launchState.launchedFromApp;
         if (launchState.launchedHasConfigurationChanged || wasLaunchedByAm) {
             EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 4359101..72b914c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -37,6 +37,13 @@
     private int mTaskPadding;
 
     public FreeformWorkspaceLayoutAlgorithm(Context context) {
+        reloadOnConfigurationChange(context);
+    }
+
+    /**
+     * Reloads the layout for the current configuration.
+     */
+    public void reloadOnConfigurationChange(Context context) {
         // This is applied to the edges of each task
         mTaskPadding = context.getResources().getDimensionPixelSize(
                 R.dimen.recents_freeform_workspace_task_padding) / 2;
@@ -72,8 +79,7 @@
                 }
                 // Bound the task width to the workspace width so that at the worst case, it will
                 // fit its own row
-                normalizedTaskWidths[i] = Math.min(rowTaskWidth,
-                        normalizedWorkspaceWidth);
+                normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
             }
 
             // Determine the scale to best fit each of the tasks in the workspace
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 37b2859..e5022a4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -105,9 +105,6 @@
             animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
                 @Override
                 public void onAnimationStarted() {
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
                     EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
 
                     if (screenPinningRequested) {
@@ -149,6 +146,10 @@
                         animStartedListener);
             }
         }
+
+        // If we are launching into another task, cancel the previous task's
+        // window transition
+        EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 5dde926..10f491e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -192,7 +192,7 @@
         // If we are already occluded by the app, then just set the default background scrim now.
         // Otherwise, defer until the enter animation completes to animate the scrim with the
         // tasks for the home animation.
-        if (launchState.launchedWhileDocking || launchState.launchedFromAppWithThumbnail
+        if (launchState.launchedWhileDocking || launchState.launchedFromApp
                 || mStack.getTaskCount() == 0) {
             mBackgroundScrim.setAlpha((int) (DEFAULT_SCRIM_ALPHA * 255));
         } else {
@@ -671,7 +671,7 @@
 
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        if (!launchState.launchedWhileDocking && !launchState.launchedFromAppWithThumbnail
+        if (!launchState.launchedWhileDocking && !launchState.launchedFromApp
                 && mStack.getTaskCount() > 0) {
             animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index d152010..758f4d8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -56,8 +56,8 @@
         /**
          * Callback to start the animation for the launch target {@link TaskView}.
          */
-        void onStartLaunchTargetEnterAnimation(int duration, boolean screenPinningEnabled,
-                ReferenceCountedTrigger postAnimationTrigger);
+        void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
+                boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger);
 
         /**
          * Callback to start the animation for the launch target {@link TaskView} when it is
@@ -141,7 +141,7 @@
                 tv.setVisibility(View.INVISIBLE);
             } else if (launchState.launchedHasConfigurationChanged) {
                 // Just load the views as-is
-            } else if (launchState.launchedFromAppWithThumbnail) {
+            } else if (launchState.launchedFromApp) {
                 if (task.isLaunchTarget) {
                     tv.onPrepareLaunchTargetForEnterAnimation();
                 } else if (currentTaskOccludesLaunchTarget) {
@@ -205,10 +205,11 @@
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
                     null);
 
-            if (launchState.launchedFromAppWithThumbnail) {
+            if (launchState.launchedFromApp) {
                 if (task.isLaunchTarget) {
-                    tv.onStartLaunchTargetEnterAnimation(taskViewEnterFromAppDuration,
-                            mStackView.mScreenPinningEnabled, postAnimationTrigger);
+                    tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
+                            taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
+                            postAnimationTrigger);
                 } else {
                     // Animate the task up if it was occluding the launch target
                     if (currentTaskOccludesLaunchTarget) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 261b6f6..b60fca8c1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -29,6 +29,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.misc.FreePathInterpolator;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
@@ -36,6 +37,7 @@
 import com.android.systemui.recents.model.TaskStack;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Used to describe a visible range that can be normalized to [0, 1].
@@ -220,6 +222,10 @@
     private Range mUnfocusedRange;
     private Range mFocusedRange;
 
+    // The initial offset from the top of the stack
+    @ViewDebug.ExportedProperty(category="recents")
+    private int mInitialTopPeekHeight;
+
     // The offset from the top when scrolled to the top of the stack
     @ViewDebug.ExportedProperty(category="recents")
     private int mFocusedTopPeekHeight;
@@ -231,6 +237,10 @@
     @ViewDebug.ExportedProperty(category="recents")
     private int mStackTopOffset;
 
+    // The height of the header bar
+    @ViewDebug.ExportedProperty(category="recents")
+    private int mHeaderBarHeight;
+
     // The offset from the bottom of the stack to the bottom of the bounds when the stack is
     // scrolled to the front
     @ViewDebug.ExportedProperty(category="recents")
@@ -249,6 +259,9 @@
     private FreePathInterpolator mUnfocusedDimCurveInterpolator;
     private FreePathInterpolator mFocusedDimCurveInterpolator;
 
+    // Indexed from the front of the stack, the normalized x in the unfocused range for each task
+    private float[] mInitialNormX;
+
     // The state of the stack focus (0..1), which controls the transition of the stack from the
     // focused to non-focused state
     @ViewDebug.ExportedProperty(category="recents")
@@ -292,23 +305,32 @@
     TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
 
     public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
-        Resources res = context.getResources();
         mContext = context;
         mCb = cb;
+        mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
+        reloadOnConfigurationChange(context);
+    }
 
+    /**
+     * Reloads the layout for the current configuration.
+     */
+    public void reloadOnConfigurationChange(Context context) {
+        Resources res = context.getResources();
         mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
                 res.getFloat(R.integer.recents_layout_focused_range_max));
         mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
                 res.getFloat(R.integer.recents_layout_unfocused_range_max));
-        mFocusState = getDefaultFocusState();
+        mFocusState = getInitialFocusState();
+        mInitialTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_initial_top_peek_size);
         mFocusedTopPeekHeight =
                 res.getDimensionPixelSize(R.dimen.recents_layout_focused_top_peek_size);
         mFocusedBottomTaskPeekHeight =
                 res.getDimensionPixelSize(R.dimen.recents_layout_focused_bottom_task_peek_size);
+        mHeaderBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
 
         mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
         mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
-        mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
+        mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
     }
 
     /**
@@ -316,7 +338,7 @@
      */
     public void reset() {
         mTaskIndexOverrideMap.clear();
-        setFocusState(getDefaultFocusState());
+        setFocusState(getInitialFocusState());
     }
 
     /**
@@ -439,46 +461,87 @@
             mTaskIndexMap.put(task.key.id, i);
         }
 
-        // Calculate the min/max scroll
-        if (getDefaultFocusState() > 0f) {
-            mMinScrollP = 0;
-            mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
-        } else {
-            if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
-                mMinScrollP = mMaxScrollP = 0;
-            } else {
-                float bottomOffsetPct = (float) (mStackBottomOffset + mTaskRect.height()) /
-                        mStackRect.height();
-                float normX = mUnfocusedCurveInterpolator.getX(bottomOffsetPct);
-                mMinScrollP = 0;
-                mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
-                        Math.max(0, mUnfocusedRange.getAbsoluteX(normX)));
-            }
-        }
-
+        // Update the freeform tasks
         if (!freeformTasks.isEmpty()) {
             mFreeformLayoutAlgorithm.update(freeformTasks, this);
-            mInitialScrollP = mMaxScrollP;
-        } else {
-            Task launchTask = stack.getLaunchTarget();
-            int launchTaskIndex = launchTask != null
-                    ? stack.indexOfStackTask(launchTask)
-                    : mNumStackTasks - 1;
-            if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
-                mInitialScrollP = mMinScrollP;
-            } else if (getDefaultFocusState() > 0f) {
-                if (launchState.launchedFromHome) {
-                    mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
-                } else {
-                    mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP,
-                            mMaxScrollP);
-                }
+        }
+
+        // Calculate the min/max/initial scroll
+        Task launchTask = stack.getLaunchTarget();
+        int launchTaskIndex = launchTask != null
+                ? stack.indexOfStackTask(launchTask)
+                : mNumStackTasks - 1;
+        if (getInitialFocusState() == STATE_FOCUSED) {
+            mMinScrollP = 0;
+            mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+            if (launchState.launchedFromHome) {
+                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
             } else {
-                float offsetPct = (float) (mTaskRect.height() / 3) / mStackRect.height();
-                float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
-                mInitialScrollP = Utilities.clamp(launchTaskIndex -
-                        mUnfocusedRange.getAbsoluteX(normX), mMinScrollP, mMaxScrollP);
+                mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
             }
+        } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+            // If there is one stack task, ignore the min/max/initial scroll positions
+            mMinScrollP = 0;
+            mMaxScrollP = 0;
+            mInitialScrollP = 0;
+        } else {
+            // Set the max scroll to be the point where the front most task is visible with the
+            // stack bottom offset
+            int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
+            float maxBottomOffsetPct = (float) maxBottomOffset / mStackRect.height();
+            float maxBottomNormX = mUnfocusedCurveInterpolator.getX(maxBottomOffsetPct);
+            mUnfocusedRange.offset(0f);
+            mMinScrollP = 0;
+            mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
+                    Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
+            boolean scrollToFront = launchState.launchedFromHome ||
+                    launchState.launchedFromAppDocked;
+            if (scrollToFront) {
+                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+            } else {
+                mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
+            }
+
+            // Set the initial scroll to the predefined state (which differs from the stack)
+            int initialPeekOffset = mStackRect.height() - mInitialTopPeekHeight;
+            float initialPeekOffsetPct = (float) initialPeekOffset / mStackRect.height();
+            float initialPeekOffsetNormX = mUnfocusedCurveInterpolator.getX(initialPeekOffsetPct);
+            float initialFocusedOffset = mStackRect.height() - mInitialTopPeekHeight -
+                    (mHeaderBarHeight * 1f) + 1;
+            float initialFocusedOffsetPct = (float) initialFocusedOffset / mStackRect.height();
+            float initialFocusedNormX = mUnfocusedCurveInterpolator.getX(initialFocusedOffsetPct);
+            int initialBottomOffset = mStackBottomOffset + mHeaderBarHeight;
+            float initialBottomOffsetPct = (float) initialBottomOffset / mStackRect.height();
+            float initialBottomNormX = mUnfocusedCurveInterpolator.getX(initialBottomOffsetPct);
+            /*
+            // If we want to offset the top card slightly
+            mInitialNormX = scrollToFront
+                    ? new float[] { initialFocusedNormX, initialPeekOffsetNormX, 0f }
+                    : new float[] { initialBottomNormX, initialFocusedNormX,
+                            initialPeekOffsetNormX, 0f };
+            */
+            mInitialNormX = scrollToFront
+                    ? new float[] { initialFocusedNormX, initialPeekOffsetNormX, 0f }
+                    : new float[] { initialBottomNormX, 0.5f, 0f };
+        }
+    }
+
+    public void updateToInitialState(List<Task> tasks) {
+        if (mInitialNormX == null) {
+            return;
+        }
+
+        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+        mUnfocusedRange.offset(0f);
+        int taskCount = tasks.size();
+        for (int i = taskCount - 1; i >= 0; i--) {
+            int indexFromFront = taskCount - i - 1;
+            if (indexFromFront >= mInitialNormX.length) {
+                break;
+            }
+            float newTaskProgress = mInitialScrollP +
+                    mUnfocusedRange.getAbsoluteX(mInitialNormX[indexFromFront]);
+            mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
         }
     }
 
@@ -500,6 +563,10 @@
         }
     }
 
+    public void clearUnfocusedTaskOverrides() {
+        mTaskIndexOverrideMap.clear();
+    }
+
     /**
      * Updates this stack when a scroll happens.
      */
@@ -523,7 +590,7 @@
             } else {
                 // Scrolling override x away from x, we should still move the scroll towards x
                 float deltaX = overrideX - x;
-                newOverrideX = Math.signum(deltaX) * (Math.abs(deltaX) - deltaScroll);
+                newOverrideX = Math.signum(deltaX) * (Math.abs(deltaX) - Math.abs(deltaScroll));
                 mTaskIndexOverrideMap.put(taskId, x + newOverrideX);
             }
         }
@@ -532,8 +599,13 @@
     /**
      * Returns the default focus state.
      */
-    public int getDefaultFocusState() {
-        return STATE_FOCUSED;
+    public int getInitialFocusState() {
+        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        if (debugFlags.isPagingEnabled()) {
+            return STATE_FOCUSED;
+        } else {
+            return STATE_UNFOCUSED;
+        }
     }
 
     /**
@@ -577,7 +649,7 @@
         // Otherwise, walk backwards in the stack and count the number of tasks and visible
         // thumbnails and add that to the total freeform task count
         TaskViewTransform tmpTransform = new TaskViewTransform();
-        Range currentRange = getDefaultFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
+        Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
         currentRange.offset(mInitialScrollP);
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_bar_height);
@@ -635,11 +707,19 @@
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform frontTransform) {
         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */);
+                false /* forceUpdate */, false /* ignoreTaskOverrides */);
+    }
+
+    public TaskViewTransform getStackTransform(Task task, float stackScroll,
+            TaskViewTransform transformOut, TaskViewTransform frontTransform,
+            boolean ignoreTaskOverrides) {
+        return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
+                false /* forceUpdate */, ignoreTaskOverrides);
     }
 
     public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
-        TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate) {
+            TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
+            boolean ignoreTaskOverrides) {
         if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
             mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
             return transformOut;
@@ -649,7 +729,9 @@
                 transformOut.reset();
                 return transformOut;
             }
-            float taskProgress = getStackScrollForTask(task);
+            float taskProgress = ignoreTaskOverrides
+                    ? mTaskIndexMap.get(task.key.id, 0)
+                    : getStackScrollForTask(task);
             getStackTransform(taskProgress, stackScroll, focusState, transformOut,
                     frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
             return transformOut;
@@ -851,8 +933,8 @@
         // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
         // task), then goes back to max dim towards the front of the stack
         p.moveTo(0f, MAX_DIM);
-        p.cubicTo(0f, 0.1f, 0.4f, 0f, 0.5f, 0f);
-        p.cubicTo(0.6f, 0f, 0.9f, MAX_DIM - 0.1f, 1f, MAX_DIM / 2f);
+        p.cubicTo(0.1f, MAX_DIM, 0.4f, 0.0f, 0.5f, 0f);
+        p.cubicTo(0.6f, 0f, 0.9f, MAX_DIM / 2f, 1f, MAX_DIM / 2f);
         return p;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index e1a81c8..c2bfc28 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -35,7 +35,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.MutableBoolean;
-import android.util.SparseBooleanArray;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -56,6 +55,7 @@
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.EnterRecentsTaskStackAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
@@ -73,7 +73,6 @@
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
-import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
 import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
@@ -274,10 +273,18 @@
     }
 
     /** Returns the task stack. */
-    TaskStack getStack() {
+    public TaskStack getStack() {
         return mStack;
     }
 
+    /**
+     * Updates this TaskStackView to the initial state.
+     */
+    public void updateToInitialState() {
+        mStackScroller.setStackScrollToInitialState();
+        mLayoutAlgorithm.updateToInitialState(mStack.getStackTasks());
+    }
+
     /** Updates the list of task views */
     void updateTaskViewsList() {
         mTaskViews.clear();
@@ -355,7 +362,6 @@
         mAwaitingFirstLayout = true;
         mEnterAnimationComplete = false;
         mUIDozeTrigger.stopDozing();
-        mUIDozeTrigger.resetTrigger();
         mStackScroller.reset();
         mLayoutAlgorithm.reset();
         readSystemFlags();
@@ -410,7 +416,7 @@
      */
     int[] computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
             ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
-            ArraySet<Task.TaskKey> ignoreTasksSet) {
+            ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides) {
         int taskCount = tasks.size();
         int[] visibleTaskRange = mTmpIntPair;
         visibleTaskRange[0] = -1;
@@ -430,7 +436,7 @@
 
             // Calculate the current and (if necessary) the target transform for the task
             transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
-                    taskTransforms.get(i), frontTransform);
+                    taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
             if (useTargetStackScroll && !transform.visible) {
                 // If we have a target stack scroll and the task is not currently visible, then we
                 // just update the transform at the new scroll
@@ -468,11 +474,13 @@
 
     /**
      * Binds the visible {@link TaskView}s at the given target scroll.
-     *
-     * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>)
      */
     void bindVisibleTaskViews(float targetStackScroll) {
-        bindVisibleTaskViews(targetStackScroll, mIgnoreTasks);
+        bindVisibleTaskViews(targetStackScroll, mIgnoreTasks, false /* ignoreTaskOverrides */);
+    }
+
+    void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
+        bindVisibleTaskViews(targetStackScroll, mIgnoreTasks, ignoreTaskOverrides);
     }
 
     /**
@@ -487,12 +495,16 @@
      *                          target stack scroll.
      * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible
      *                       {@link TaskView}s
+     * @param ignoreTaskOverrides If set, the visible task computation will get the transforms for
+     *                            tasks at their non-overridden task progress
      */
-    void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) {
+    void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet,
+            boolean ignoreTaskOverrides) {
         // Get all the task transforms
         ArrayList<Task> tasks = mStack.getStackTasks();
         int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
-                mStackScroller.getStackScroll(), targetStackScroll, ignoreTasksSet);
+                mStackScroller.getStackScroll(), targetStackScroll, ignoreTasksSet,
+                ignoreTaskOverrides);
 
         // Return all the invisible children to the pool
         mTmpTaskViewMap.clear();
@@ -605,7 +617,8 @@
         cancelAllTaskViewAnimations();
 
         // Synchronize the current set of TaskViews
-        bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet);
+        bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet,
+                false /* ignoreTaskOverrides */);
 
         // Animate them to their final transforms with the given animation
         List<TaskView> taskViews = getTaskViews();
@@ -657,7 +670,8 @@
                 transform.fillIn(tv);
             } else {
                 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
-                        focusState, transform, null, true /* forceUpdate */);
+                        focusState, transform, null, true /* forceUpdate */,
+                        false /* ignoreTaskOverrides */);
             }
             transform.visible = true;
         }
@@ -674,7 +688,7 @@
             Task task = tasks.get(i);
             TaskViewTransform transform = transformsOut.get(i);
             mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
-                    true /* forceUpdate */);
+                    true /* forceUpdate */, true /* ignoreTaskOverrides */);
             transform.visible = true;
         }
     }
@@ -759,9 +773,7 @@
                 }
             }
             tv.getViewBounds().setClipBottom(clipBottom);
-            if (!config.useHardwareLayers) {
-                tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
-            }
+            tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
             prevVisibleTv = tv;
         }
         mTaskViewsClipDirty = false;
@@ -860,6 +872,7 @@
                     cancelAllTaskViewAnimations();
                 }
 
+                mLayoutAlgorithm.clearUnfocusedTaskOverrides();
                 willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
                         requestViewFocus);
             } else {
@@ -1162,11 +1175,16 @@
 
         // If this is the first layout, then scroll to the front of the stack, then update the
         // TaskViews with the stack so that we can lay them out
-        if (mAwaitingFirstLayout) {
-            mStackScroller.setStackScrollToInitialState();
+        // TODO: The second check is a workaround for wacky layouts that we get while docking via
+        //       long pressing the recents button
+        if (mAwaitingFirstLayout ||
+                (mStackScroller.getStackScroll() == mLayoutAlgorithm.mInitialScrollP)) {
+            updateToInitialState();
         }
+
         // Rebind all the views, including the ignore ones
-        bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET);
+        bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET,
+                false /* ignoreTaskOverrides */);
 
         // Measure each of the TaskViews
         mTmpTaskViews.clear();
@@ -1458,7 +1476,7 @@
         Recents.getTaskLoader().loadTaskData(task, true /* fetchAndInvalidateThumbnails */);
 
         // If the doze trigger has already fired, then update the state for this task view
-        if (mUIDozeTrigger.hasTriggered()) {
+        if (mUIDozeTrigger.isAsleep()) {
             tv.setNoUserInteractionState();
         }
 
@@ -1859,6 +1877,12 @@
         });
     }
 
+    public final void onBusEvent(ConfigurationChangedEvent event) {
+        mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
+        mLayoutAlgorithm.initialize(mStackBounds,
+                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+    }
+
     /**
      * Removes the task from the stack, and updates the focus to the next task in the stack if the
      * removed TaskView was focused.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 333df9d..ad46abd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -115,13 +115,8 @@
      * @return whether the stack progress changed.
      */
     public boolean setStackScrollToInitialState() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
         float prevStackScrollP = mStackScrollP;
-        if (ssp.hasDockedTask()) {
-            setStackScroll(mLayoutAlgorithm.mMaxScrollP);
-        } else {
-            setStackScroll(mLayoutAlgorithm.mInitialScrollP);
-        }
+        setStackScroll(mLayoutAlgorithm.mInitialScrollP);
         return Float.compare(prevStackScrollP, mStackScrollP) != 0;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 20933ee..52f8fc8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -446,7 +446,7 @@
             }
 
             // Pick up the newly visible views, not including the deleting tasks
-            mSv.bindVisibleTaskViews(newStackScroll);
+            mSv.bindVisibleTaskViews(newStackScroll, true /* ignoreTaskOverrides */);
 
             // Get the final set of task transforms (with task removed)
             mSv.getLayoutTaskTransforms(newStackScroll, TaskStackLayoutAlgorithm.STATE_UNFOCUSED,
@@ -486,6 +486,7 @@
         mSv.getScroller().setStackScroll(mTargetStackScroll, null);
         // Update the focus state to the final focus state
         mSv.getStackAlgorithm().setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
+        mSv.getStackAlgorithm().clearUnfocusedTaskOverrides();
         // Remove the task view from the stack
         EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv));
         // Stop tracking this deletion animation
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 2e7c7f2..e9c7ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -77,6 +77,24 @@
 
     /**
      * The dim overlay is generally calculated from the task progress, but occasionally (like when
+     * launching) needs to be animated independently of the task progress.  This call is only used
+     * when animating the task into Recents, when the header dim is already applied
+     */
+    public static final Property<TaskView, Float> DIM_ALPHA_WITHOUT_HEADER =
+            new FloatProperty<TaskView>("dimAlphaWithoutHeader") {
+                @Override
+                public void setValue(TaskView tv, float dimAlpha) {
+                    tv.setDimAlphaWithoutHeader(dimAlpha);
+                }
+
+                @Override
+                public Float get(TaskView tv) {
+                    return tv.getDimAlpha();
+                }
+            };
+
+    /**
+     * The dim overlay is generally calculated from the task progress, but occasionally (like when
      * launching) needs to be animated independently of the task progress.
      */
     public static final Property<TaskView, Float> DIM_ALPHA =
@@ -388,21 +406,17 @@
      * Sets the current dim.
      */
     public void setDimAlpha(float dimAlpha) {
-        RecentsConfiguration config = Recents.getConfiguration();
-
-        int dimAlphaInt = (int) (dimAlpha * 255);
         mDimAlpha = dimAlpha;
-        if (config.useHardwareLayers) {
-            // Defer setting hardware layers if we have not yet measured, or there is no dim to draw
-            if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
-                mDimColorFilter.setColor(Color.argb(dimAlphaInt, 0, 0, 0));
-                mDimLayerPaint.setColorFilter(mDimColorFilter);
-                mContent.setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
-            }
-        } else {
-            mThumbnailView.setDimAlpha(dimAlpha);
-            mHeaderView.setDimAlpha(dimAlpha);
-        }
+        mThumbnailView.setDimAlpha(dimAlpha);
+        mHeaderView.setDimAlpha(dimAlpha);
+    }
+
+    /**
+     * Sets the current dim without updating the header's dim.
+     */
+    public void setDimAlphaWithoutHeader(float dimAlpha) {
+        mDimAlpha = dimAlpha;
+        mThumbnailView.setDimAlpha(dimAlpha);
     }
 
     /**
@@ -413,25 +427,6 @@
     }
 
     /**
-     * Animates the dim to the given value.
-     */
-    void animateDimAlpha(float toDimAlpha, AnimationProps animation) {
-        // Animate the dim into view as well
-        if (Float.compare(toDimAlpha, getDimAlpha()) != 0) {
-            Animator anim = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
-                    DIM_ALPHA, getDimAlpha(), toDimAlpha));
-            if (animation.getListener() != null) {
-                anim.addListener(animation.getListener());
-            }
-            anim.start();
-        } else {
-            if (animation.getListener() != null) {
-                animation.getListener().onAnimationEnd(null);
-            }
-        }
-    }
-
-    /**
      * Explicitly sets the focused state of this task.
      */
     public void setFocusedState(boolean isFocused, boolean requestViewFocus) {
@@ -517,18 +512,20 @@
     @Override
     public void onPrepareLaunchTargetForEnterAnimation() {
         // These values will be animated in when onStartLaunchTargetEnterAnimation() is called
-        setDimAlpha(0);
+        setDimAlphaWithoutHeader(0);
         mActionButtonView.setAlpha(0f);
     }
 
     @Override
-    public void onStartLaunchTargetEnterAnimation(int duration, boolean screenPinningEnabled,
-            ReferenceCountedTrigger postAnimationTrigger) {
-        // Un-dim the view before/while launching the target
-        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT)
-                .setListener(postAnimationTrigger.decrementOnAnimationEnd());
+    public void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
+            boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger) {
+        // Dim the view after the app window transitions down into recents
         postAnimationTrigger.increment();
-        animateDimAlpha(0, animation);
+        AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
+        Animator anim = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
+                DIM_ALPHA_WITHOUT_HEADER, getDimAlpha(), transform.dimAlpha));
+        anim.addListener(postAnimationTrigger.decrementOnAnimationEnd());
+        anim.start();
 
         if (screenPinningEnabled) {
             showActionButton(true /* fadeIn */, duration /* fadeInDuration */);
@@ -540,7 +537,9 @@
             ReferenceCountedTrigger postAnimationTrigger) {
         // Un-dim the view before/while launching the target
         AnimationProps animation = new AnimationProps(duration, Interpolators.ALPHA_OUT);
-        animateDimAlpha(0, animation);
+        Animator anim = animation.apply(AnimationProps.DIM_ALPHA, ObjectAnimator.ofFloat(this,
+                DIM_ALPHA, getDimAlpha(), 0));
+        anim.start();
 
         postAnimationTrigger.increment();
         hideActionButton(true /* fadeOut */, duration,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 05a8527..b2a7d90 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -347,9 +347,11 @@
      * Sets the dim alpha, only used when we are not using hardware layers.
      * (see RecentsConfiguration.useHardwareLayers)
      */
-    void setDimAlpha(float dimAlpha) {
-        mDimAlpha = dimAlpha;
-        updateBackgroundColor(mBackground.getColor(), dimAlpha);
+    public void setDimAlpha(float dimAlpha) {
+        if (Float.compare(mDimAlpha, dimAlpha) != 0) {
+            mDimAlpha = dimAlpha;
+            updateBackgroundColor(mBackground.getColor(), dimAlpha);
+        }
     }
 
     /**
@@ -377,7 +379,9 @@
         int primaryColor = disabledInSafeMode
                 ? mDisabledTaskBarBackgroundColor
                 : t.colorPrimary;
-        updateBackgroundColor(primaryColor, mDimAlpha);
+        if (mBackground.getColor() != primaryColor) {
+            updateBackgroundColor(primaryColor, mDimAlpha);
+        }
         if (t.icon != null) {
             mIconView.setImageDrawable(t.icon);
         }