Merge "Don't show the discovery bounce when running test harness" into ub-launcher3-master
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 08c740c..02b4379 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -23,6 +23,7 @@
 
     <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
 
+    <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index b3025ff..18ddeee 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/drawable/task_thumbnail_background.xml b/quickstep/res/drawable/task_thumbnail_background.xml
deleted file mode 100644
index f1f48ac..0000000
--- a/quickstep/res/drawable/task_thumbnail_background.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
-    <corners android:radius="2dp" />
-</shape>
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index 78238fa..9f4f8a1 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -26,32 +26,6 @@
 
     <com.android.launcher3.uioverrides.WorkspaceCard
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:paddingTop="@dimen/task_thumbnail_top_margin" >
-
-        <View
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:id="@+id/workspace_click_target" />
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:id="@+id/widget_button"
-            android:background="@drawable/bg_workspace_card_button" >
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/widget_button_text"
-                android:drawableStart="@drawable/ic_widget"
-                android:textColor="?attr/workspaceTextColor"
-                android:drawableTint="?attr/workspaceTextColor"
-                android:gravity="center"
-                android:layout_gravity="center"
-                android:drawablePadding="20dp" />
-        </FrameLayout>
-
-    </com.android.launcher3.uioverrides.WorkspaceCard>
+        android:layout_height="match_parent" />
 
 </com.android.quickstep.RecentsView>
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 839d934..91b6aa3 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
      Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,23 +13,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.TaskView
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.quickstep.TaskView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:elevation="4dp">
 
     <com.android.quickstep.TaskThumbnailView
         android:id="@+id/snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/task_thumbnail_top_margin"
-        android:scaleType="matrix"
-        android:background="@drawable/task_thumbnail_background"
-        android:elevation="4dp" />
+        android:layout_marginTop="@dimen/task_thumbnail_top_margin" />
+
     <ImageView
         android:id="@+id/icon"
         android:layout_width="@dimen/task_thumbnail_icon_size"
         android:layout_height="@dimen/task_thumbnail_icon_size"
-        android:layout_gravity="top|center_horizontal"
-        android:elevation="5dp"/>
+        android:layout_gravity="top|center_horizontal" />
 </com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 222a3f4..9ef8e82 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -19,6 +19,9 @@
     <dimen name="task_thumbnail_top_margin">24dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
     <dimen name="task_menu_background_radius">12dp</dimen>
+    <dimen name="task_corner_radius">2dp</dimen>
+    <dimen name="task_fade_length">20dp</dimen>
+
 
     <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
     <dimen name="quickstep_fling_min_velocity">250dp</dimen>
@@ -27,4 +30,7 @@
 
     <!-- TODO: This can be calculated using other resource values -->
     <dimen name="all_apps_search_box_full_height">90dp</dimen>
+
+    <dimen name="drag_layer_trans_y">25dp</dimen>
+
 </resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java
new file mode 100644
index 0000000..8ee1b85
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManager.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2018 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.launcher3;
+
+import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
+import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
+import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+
+import com.android.launcher3.InsettableFrameLayout.LayoutParams;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+/**
+ * Manages the opening app animations from Launcher.
+ */
+public class LauncherAppTransitionManager {
+
+    private static final int REFRESH_RATE_MS = 16;
+
+    private final DragLayer mDragLayer;
+    private final Launcher mLauncher;
+    private final DeviceProfile mDeviceProfile;
+
+    private final float mDragLayerTransY;
+
+    private ImageView mFloatingView;
+
+    public LauncherAppTransitionManager(Launcher launcher) {
+        mLauncher = launcher;
+        mDragLayer = launcher.getDragLayer();
+        mDeviceProfile = launcher.getDeviceProfile();
+
+        mDragLayerTransY =
+                launcher.getResources().getDimensionPixelSize(R.dimen.drag_layer_trans_y);
+    }
+
+    public Bundle getActivityLauncherOptions(View v) {
+        RemoteAnimationRunnerCompat runner = new RemoteAnimationRunnerCompat() {
+            @Override
+            public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
+                    Runnable finishedCallback) {
+                // Post at front of queue ignoring sync barriers to make sure it gets processed
+                // before the next frame.
+                postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
+                    AnimatorSet both = new AnimatorSet();
+                    both.play(getLauncherAnimators(v));
+                    both.play(getAppWindowAnimators(v, targets));
+                    both.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            // Reset launcher to normal state
+                            v.setVisibility(View.VISIBLE);
+                            ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+
+                            mDragLayer.setAlpha(1f);
+                            mDragLayer.setTranslationY(0f);
+                            finishedCallback.run();
+                        }
+                    });
+                    both.start();
+                    // Because t=0 has the app icon in its original spot, we can skip the first
+                    // frame and have the same movement one frame earlier.
+                    both.setCurrentPlayTime(REFRESH_RATE_MS);
+                });
+            }
+
+            @Override
+            public void onAnimationCancelled() {
+            }
+        };
+
+        return ActivityOptionsCompat.makeRemoteAnimation(
+                new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
+    }
+
+    private AnimatorSet getLauncherAnimators(View v) {
+        AnimatorSet launcherAnimators = new AnimatorSet();
+        launcherAnimators.play(getHideLauncherAnimator());
+        launcherAnimators.play(getAppIconAnimator(v));
+        return launcherAnimators;
+    }
+
+    private AnimatorSet getHideLauncherAnimator() {
+        AnimatorSet hideLauncher = new AnimatorSet();
+
+        // Animate Launcher so that it moves downwards and fades out.
+        ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, 1f, 0f);
+        dragLayerAlpha.setDuration(217);
+        dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
+        ObjectAnimator dragLayerTransY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, 0,
+                mDragLayerTransY);
+        dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        dragLayerTransY.setDuration(350);
+
+        hideLauncher.play(dragLayerAlpha);
+        hideLauncher.play(dragLayerTransY);
+        return hideLauncher;
+    }
+
+    private AnimatorSet getAppIconAnimator(View v) {
+        // Create a copy of the app icon
+        mFloatingView = new ImageView(mLauncher);
+        Bitmap iconBitmap = ((FastBitmapDrawable) ((BubbleTextView) v).getIcon()).getBitmap();
+        mFloatingView.setImageDrawable(new FastBitmapDrawable(iconBitmap));
+
+        // Position the copy of the app icon exactly on top of the original
+        Rect rect = new Rect();
+        mDragLayer.getDescendantRectRelativeToSelf(v, rect);
+        int viewLocationLeft = rect.left;
+        int viewLocationTop = rect.top;
+
+        ((BubbleTextView) v).getIconBounds(rect);
+        LayoutParams lp = new LayoutParams(rect.width(), rect.height());
+        lp.ignoreInsets = true;
+        lp.leftMargin = viewLocationLeft + rect.left;
+        lp.topMargin = viewLocationTop + rect.top;
+        mFloatingView.setLayoutParams(lp);
+
+        // Swap the two views in place.
+        ((ViewGroup) mDragLayer.getParent()).addView(mFloatingView);
+        v.setVisibility(View.INVISIBLE);
+
+        AnimatorSet appIconAnimatorSet = new AnimatorSet();
+        // Animate the app icon to the center
+        float centerX = mDeviceProfile.widthPx / 2;
+        float centerY = mDeviceProfile.heightPx / 2;
+        float dX = centerX - lp.leftMargin - (lp.width / 2);
+        float dY = centerY - lp.topMargin - (lp.height / 2);
+        ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX);
+        ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY);
+
+        // Adjust the duration to change the "curve" of the app icon to the center.
+        boolean isBelowCenterY = lp.topMargin < centerY;
+        x.setDuration(isBelowCenterY ? 500 : 233);
+        y.setDuration(isBelowCenterY ? 233 : 500);
+        appIconAnimatorSet.play(x);
+        appIconAnimatorSet.play(y);
+
+        // Scale the app icon to take up the entire screen. This simplifies the math when
+        // animating the app window position / scale.
+        float maxScaleX = mDeviceProfile.widthPx / (float) rect.width();
+        float maxScaleY = mDeviceProfile.heightPx / (float) rect.height();
+        float scale = Math.max(maxScaleX, maxScaleY);
+        ObjectAnimator sX = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_X, 1f, scale);
+        ObjectAnimator sY = ObjectAnimator.ofFloat(mFloatingView, View.SCALE_Y, 1f, scale);
+        sX.setDuration(500);
+        sY.setDuration(500);
+        appIconAnimatorSet.play(sX);
+        appIconAnimatorSet.play(sY);
+
+        // Fade out the app icon.
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
+        alpha.setStartDelay(17);
+        alpha.setDuration(33);
+        appIconAnimatorSet.play(alpha);
+
+        for (Animator a : appIconAnimatorSet.getChildAnimations()) {
+            a.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+        }
+        return appIconAnimatorSet;
+    }
+
+    private ValueAnimator getAppWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
+        Rect iconBounds = new Rect();
+        ((BubbleTextView) v).getIconBounds(iconBounds);
+        int[] floatingViewBounds = new int[2];
+
+        Rect crop = new Rect();
+        Matrix matrix = new Matrix();
+
+        ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+        appAnimator.setDuration(500);
+        appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            boolean isFirstFrame = true;
+
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                final float percent = animation.getAnimatedFraction();
+                final float easePercent = Interpolators.AGGRESSIVE_EASE.getInterpolation(percent);
+
+                // Calculate app icon size.
+                float iconWidth = iconBounds.width() * mFloatingView.getScaleX();
+                float iconHeight = iconBounds.height() * mFloatingView.getScaleY();
+
+                // Scale the app window to match the icon size.
+                float scaleX = iconWidth / mDeviceProfile.widthPx;
+                float scaleY = iconHeight / mDeviceProfile.heightPx;
+                float scale = Math.min(1f, Math.min(scaleX, scaleY));
+                matrix.setScale(scale, scale);
+
+                // Position the scaled window on top of the icon
+                int deviceWidth = mDeviceProfile.widthPx;
+                int deviceHeight = mDeviceProfile.heightPx;
+                float scaledWindowWidth = deviceWidth * scale;
+                float scaledWindowHeight = deviceHeight * scale;
+
+                float offsetX = (scaledWindowWidth - iconWidth) / 2;
+                float offsetY = (scaledWindowHeight - iconHeight) / 2;
+                mFloatingView.getLocationInWindow(floatingViewBounds);
+                float transX0 = floatingViewBounds[0] - offsetX;
+                float transY0 = floatingViewBounds[1] - offsetY;
+                matrix.postTranslate(transX0, transY0);
+
+                // Fade in the app window.
+                float alphaDelay = 0;
+                float alphaDuration = 50;
+                float alpha = getValue(1f, 0f, alphaDelay, alphaDuration,
+                        appAnimator.getDuration() * percent, Interpolators.AGGRESSIVE_EASE);
+
+                // Animate the window crop so that it starts off as a square, and then reveals
+                // horizontally.
+                float cropHeight = deviceHeight * easePercent + deviceWidth * (1 - easePercent);
+                float initialTop = (deviceHeight - deviceWidth) / 2f;
+                crop.left = 0;
+                crop.top = (int) (initialTop * (1 - easePercent));
+                crop.right = deviceWidth;
+                crop.bottom = (int) (crop.top + cropHeight);
+
+                TransactionCompat t = new TransactionCompat();
+                for (RemoteAnimationTargetCompat target : targets) {
+                    if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+                        t.setAlpha(target.leash, alpha);
+                        t.setMatrix(target.leash, matrix);
+                        t.setWindowCrop(target.leash, crop);
+                        Surface surface = getSurface(mFloatingView);
+                        t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
+                    }
+                    if (isFirstFrame) {
+                        t.show(target.leash);
+                    }
+                }
+                t.apply();
+
+                matrix.reset();
+                isFirstFrame = false;
+            }
+
+            /**
+             * Helper method that allows us to get interpolated values for embedded
+             * animations with a delay and/or different duration.
+             */
+            private float getValue(float start, float end, float delay, float duration,
+                                   float currentPlayTime, Interpolator i) {
+                float time = Math.max(0, currentPlayTime - delay);
+                float newPercent = Math.min(1f, time / duration);
+                newPercent = i.getInterpolation(newPercent);
+                return start * newPercent + end * (1 - newPercent);
+            }
+        });
+        return appAnimator;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
index c1b26d4..356a144 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/EdgeSwipeController.java
@@ -29,9 +29,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.anim.SpringAnimationHandler;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.VerticalSwipeController;
 import com.android.quickstep.RecentsView;
 
@@ -40,7 +37,7 @@
  */
 public class EdgeSwipeController extends VerticalSwipeController {
 
-    private final Rect mTempRect = new Rect();
+    private static final Rect sTempRect = new Rect();
 
     public EdgeSwipeController(Launcher l) {
         super(l, NORMAL, OVERVIEW, l.getDeviceProfile().isVerticalBarLayout()
@@ -78,18 +75,22 @@
 
     @Override
     protected float getShiftRange() {
-        RecentsView.getPageRect(mLauncher, mTempRect);
-        DragLayer dl = mLauncher.getDragLayer();
+        return getShiftRange(mLauncher);
+    }
+
+    public static float getShiftRange(Launcher launcher) {
+        RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, sTempRect);
+        DragLayer dl = launcher.getDragLayer();
         Rect insets = dl.getInsets();
 
-        if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
+        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             if (insets.left > insets.right) {
-                return insets.left + mTempRect.left;
+                return insets.left + sTempRect.left;
             } else {
-                return dl.getWidth() - mTempRect.right + insets.right;
+                return dl.getWidth() - sTempRect.right + insets.right;
             }
         } else {
-            return dl.getHeight() - mTempRect.bottom + insets.bottom;
+            return dl.getHeight() - sTempRect.bottom + insets.bottom;
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 2e5e75e..68f6eed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -36,8 +36,6 @@
  */
 public class OverviewState extends LauncherState {
 
-    public static final float WORKSPACE_SCALE_ON_SCROLL = 0.9f;
-
     private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
 
     public OverviewState(int id) {
@@ -47,15 +45,15 @@
     @Override
     public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
         Rect pageRect = new Rect();
-        RecentsView.getPageRect(launcher, pageRect);
+        RecentsView.getScaledDownPageRect(launcher.getDeviceProfile(), launcher, pageRect);
+        RecentsView rv = launcher.getOverviewPanel();
+
         if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
             return super.getWorkspaceScaleAndTranslation(launcher);
         }
 
-        RecentsView rv = launcher.getOverviewPanel();
         float overlap = 0;
         if (rv.getCurrentPage() >= rv.getFirstTaskIndex()) {
-            Utilities.scaleRectAboutCenter(pageRect, WORKSPACE_SCALE_ON_SCROLL);
             overlap = launcher.getResources().getDimension(R.dimen.workspace_overview_offset_x);
         }
         return getScaleAndTranslationForPageRect(launcher, overlap, pageRect);
@@ -99,21 +97,23 @@
         float childWidth = ws.getNormalChildWidth();
         float childHeight = ws.getNormalChildHeight();
 
+        float scale = pageRect.height() / childHeight;
         Rect insets = launcher.getDragLayer().getInsets();
-        float scale = Math.min(pageRect.width() / childWidth, pageRect.height() / childHeight);
 
-        float halfHeight = ws.getHeight() / 2;
+        float halfHeight = ws.getExpectedHeight() / 2;
         float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
         float translationY = pageRect.top - childTop;
 
-        float halfWidth = ws.getWidth() / 2;
-        float translationX;
+        // Align the workspace horizontally centered with the task rect
+        float halfWidth = ws.getExpectedWidth() / 2;
+        float childCenter = halfWidth -
+                scale * (halfWidth - ws.getPaddingLeft() - insets.left - childWidth / 2);
+        float translationX = pageRect.exactCenterX() - childCenter;
+
         if (Utilities.isRtl(launcher.getResources())) {
-            float childRight = halfWidth + scale * (halfWidth - ws.getPaddingRight() - insets.right);
-            translationX = childRight - pageRect.right - offsetX / scale;
+            translationX -= offsetX / scale;
         } else {
-            float childLeft = halfWidth - scale * (halfWidth - ws.getPaddingLeft() - insets.left);
-            translationX = pageRect.left - childLeft + offsetX / scale;
+            translationX += offsetX / scale;
         }
 
         return new float[] {scale, translationX, translationY};
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
index 335077a..1fd541a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.util.TouchController;
@@ -168,7 +167,7 @@
         return mDetector.onTouchEvent(ev);
     }
 
-    private void reinitAnimationController(boolean goingUp) {
+    private void reInitAnimationController(boolean goingUp) {
         if (!goingUp && !mSwipeDownEnabled) {
             goingUp = true;
         }
@@ -191,20 +190,7 @@
             if (goingUp) {
                 mEndDisplacement = -range;
             } else {
-                View ws = mLauncher.getWorkspace();
-                mTempCords[1] = ws.getHeight() - ws.getPaddingBottom();
-                dl.getDescendantCoordRelativeToSelf(ws, mTempCords);
-
-                float distance = mTempCords[1];
-                if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
-                    mTempCords[1] = 0;
-                    dl.getDescendantCoordRelativeToSelf(mLauncher.getHotseat(), mTempCords);
-                    distance = mTempCords[1] - distance;
-                } else {
-                    distance = dl.getHeight() - distance;
-                }
-
-                mEndDisplacement = distance;
+                mEndDisplacement = EdgeSwipeController.getShiftRange(mLauncher);
             }
         } else {
             if (goingUp) {
@@ -240,7 +226,7 @@
     @Override
     public void onDragStart(boolean start) {
         if (mCurrentAnimation == null) {
-            reinitAnimationController(mDetector.wasInitialTouchPositive());
+            reInitAnimationController(mDetector.wasInitialTouchPositive());
             mDisplacementShift = 0;
         } else {
             mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
@@ -254,7 +240,7 @@
         boolean isGoingUp =
                 totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
         if (isGoingUp != mCurrentAnimationIsGoingUp) {
-            reinitAnimationController(isGoingUp);
+            reInitAnimationController(isGoingUp);
         }
         mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
         return true;
@@ -276,7 +262,7 @@
                     // Not allowed
                     goingToEnd = false;
                 } else {
-                    reinitAnimationController(goingUp);
+                    reInitAnimationController(goingUp);
                     goingToEnd = true;
                 }
             } else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index e848688..67a7d6a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -19,11 +19,14 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.os.Bundle;
+import android.view.View;
 import android.view.View.AccessibilityDelegate;
 import android.widget.PopupMenu;
 import android.widget.Toast;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppTransitionManager;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
@@ -102,4 +105,8 @@
         RecentsView recents = launcher.getOverviewPanel();
         recents.reset();
     }
+
+    public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
+        return new LauncherAppTransitionManager(launcher).getActivityLauncherOptions(v);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
index 4816e2a..92a09dd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/WorkspaceCard.java
@@ -16,170 +16,80 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.uioverrides.OverviewState.WORKSPACE_SCALE_ON_SCROLL;
 import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
 
-import android.animation.FloatArrayEvaluator;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.widget.FrameLayout;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.quickstep.RecentsView;
 import com.android.quickstep.RecentsView.PageCallbacks;
 import com.android.quickstep.RecentsView.ScrollState;
 
-public class WorkspaceCard extends FrameLayout implements PageCallbacks, OnClickListener {
+public class WorkspaceCard extends View implements PageCallbacks, OnClickListener {
 
     private final Rect mTempRect = new Rect();
-    private final float[] mEvaluatedFloats = new float[3];
-    private final FloatArrayEvaluator mEvaluator = new FloatArrayEvaluator(mEvaluatedFloats);
-
-    // UI related information
-    private float[] mScaleAndTranslatePage0, mScaleAndTranslatePage1;
-    private boolean mUIDataValid = false;
 
     private Launcher mLauncher;
     private Workspace mWorkspace;
 
+    private float mLinearInterpolationForPage2 = 1;
+    private float mTranslateXPage0, mTranslateXPage1;
+    private float mExtraScrollShift;
+
     private boolean mIsWorkspaceScrollingEnabled;
 
-    private View mWorkspaceClickTarget;
-    private View mWidgetsButton;
-
-    private boolean mLayoutHorizontal;
-
     public WorkspaceCard(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public WorkspaceCard(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public WorkspaceCard(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mWorkspaceClickTarget = findViewById(R.id.workspace_click_target);
-        mWidgetsButton = findViewById(R.id.widget_button);
-
-        mWorkspaceClickTarget.setOnClickListener(this);
-        mWidgetsButton.setOnClickListener(this);
         setOnClickListener(this);
     }
 
+    /**
+     * Draw nothing.
+     */
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // We measure the dimensions of the PagedView to be larger than the pages so that when we
-        // zoom out (and scale down), the view is still contained in the parent
-        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            return;
-        }
-
-        // Return early if we aren't given a proper dimension
-        if (widthSize <= 0 || heightSize <= 0) {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            return;
-        }
-
-        float workspaceWidth = mWorkspace.getNormalChildWidth();
-        float workspaceHeight = mWorkspace.getNormalChildHeight();
-
-        int availableWidth = widthSize - getPaddingLeft() - getPaddingRight();
-        float scaleX = availableWidth / workspaceWidth;
-
-        int availableHeight = heightSize - getPaddingTop() - getPaddingBottom();
-        float scaleY = availableHeight / workspaceHeight;
-
-        if (scaleX < scaleY) {
-            mLayoutHorizontal = false;
-            int childWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY);
-
-            int pageHeight = Math.round(workspaceHeight * scaleX);
-            mWorkspaceClickTarget.measure(childWidthSpec,
-                    MeasureSpec.makeMeasureSpec(pageHeight, MeasureSpec.EXACTLY));
-
-            int buttonHeight = availableHeight - pageHeight;
-            mWidgetsButton.measure(childWidthSpec,
-                    MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY));
-        } else {
-            mLayoutHorizontal = true;
-            int childHeightSpec = MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.EXACTLY);
-
-            int pageWidth = Math.round(workspaceWidth * scaleY);
-            mWorkspaceClickTarget.measure(
-                    MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY), childHeightSpec);
-
-            int buttonWidth = availableWidth - pageWidth;
-            mWidgetsButton.measure(
-                    MeasureSpec.makeMeasureSpec(buttonWidth, MeasureSpec.EXACTLY), childHeightSpec);
-        }
-        setMeasuredDimension(widthSize, heightSize);
-    }
+    public void draw(Canvas canvas) { }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int x = getPaddingLeft();
-        int y = getPaddingTop();
+        super.onLayout(changed, left, top, right, bottom);
 
-        if (mLayoutHorizontal) {
-            final View first, second;
-            if (Utilities.isRtl(getResources())) {
-                first = mWidgetsButton;
-                second = mWorkspaceClickTarget;
-            } else {
-                first = mWorkspaceClickTarget;
-                second = mWidgetsButton;
-            }
-            int x2 = x + first.getMeasuredWidth();
-            first.layout(x, y,
-                    x2, y + first.getMeasuredHeight());
-            second.layout(x2, y,
-                    x2 + second.getMeasuredWidth(),
-                    y + second.getMeasuredHeight());
-        } else {
-            int y2 = y + mWorkspaceClickTarget.getMeasuredHeight();
-            mWorkspaceClickTarget.layout(x, y,
-                    x + mWorkspaceClickTarget.getMeasuredWidth(), y2);
-            mWidgetsButton.layout(x, y2,
-                    x + mWidgetsButton.getMeasuredWidth(),
-                    y2 + mWidgetsButton.getMeasuredHeight());
+        // Initiate data
+        mLinearInterpolationForPage2 = RecentsView.getScaledDownPageRect(
+                mLauncher.getDeviceProfile(), mLauncher, mTempRect);
+
+        float[] scale = OverviewState.getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
+        mTranslateXPage0 = scale[1];
+        mTranslateXPage1 = OverviewState
+                .getScaleAndTranslationForPageRect(mLauncher,
+                        getResources().getDimension(R.dimen.workspace_overview_offset_x),
+                        mTempRect)[1];
+
+        mExtraScrollShift = 0;
+        if (mWorkspace != null && getWidth() > 0) {
+            float workspaceWidth = mWorkspace.getNormalChildWidth() * scale[0];
+            mExtraScrollShift = (workspaceWidth - getWidth()) / 2;
+            setScaleX(workspaceWidth / getWidth());
         }
-
-        mUIDataValid = false;
     }
 
     @Override
     public void onClick(View view) {
-        if (view == mWorkspaceClickTarget || view == this) {
-            mLauncher.getStateManager().goToState(NORMAL);
-        } else if (view == mWidgetsButton) {
-            WidgetsFullSheet.show(mLauncher, true);
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        super.onSizeChanged(w, h, oldw, oldh);
-        mUIDataValid = false;
+        mLauncher.getStateManager().goToState(NORMAL);
     }
 
     public void setup(Launcher launcher) {
@@ -194,41 +104,24 @@
     @Override
     public int onPageScroll(ScrollState scrollState) {
         float factor = scrollState.linearInterpolation;
-        float scale = factor * WORKSPACE_SCALE_ON_SCROLL + (1 - factor);
-        setScaleX(scale);
-        setScaleY(scale);
-
         float translateX = scrollState.distanceFromScreenCenter;
         if (mIsWorkspaceScrollingEnabled) {
-            initUiData();
-
-            mEvaluator.evaluate(factor, mScaleAndTranslatePage0, mScaleAndTranslatePage1);
-            mWorkspace.setScaleX(mEvaluatedFloats[0]);
-            mWorkspace.setScaleY(mEvaluatedFloats[0]);
-            mWorkspace.setTranslationX(mEvaluatedFloats[1]);
-            mWorkspace.setTranslationY(mEvaluatedFloats[2]);
-            translateX += mEvaluatedFloats[1] - mScaleAndTranslatePage0[1];
+            float shift = factor * (mTranslateXPage1 - mTranslateXPage0);
+            mWorkspace.setTranslationX(shift + mTranslateXPage0);
+            translateX += shift;
         }
 
         setTranslationX(translateX);
 
-        return SCROLL_TYPE_WORKSPACE;
-    }
-
-    private void initUiData() {
-        if (mUIDataValid && mScaleAndTranslatePage0 != null) {
-            return;
+        // If the workspace card is still the first page, shift all the other pages.
+        if (scrollState.linearInterpolation > mLinearInterpolationForPage2) {
+            scrollState.prevPageExtraWidth = 0;
+        } else if (mLinearInterpolationForPage2 > 0) {
+            scrollState.prevPageExtraWidth = mExtraScrollShift *
+                    (1 - scrollState.linearInterpolation / mLinearInterpolationForPage2);
+        } else {
+            scrollState.prevPageExtraWidth = mExtraScrollShift;
         }
-
-        float overlap = getResources().getDimension(R.dimen.workspace_overview_offset_x);
-
-        RecentsView.getPageRect(mLauncher, mTempRect);
-        mScaleAndTranslatePage0 = OverviewState
-                .getScaleAndTranslationForPageRect(mLauncher, 0, mTempRect);
-        Rect scaledDown = new Rect(mTempRect);
-        Utilities.scaleRectAboutCenter(scaledDown, WORKSPACE_SCALE_ON_SCROLL);
-        mScaleAndTranslatePage1 = OverviewState
-                .getScaleAndTranslationForPageRect(mLauncher, overlap, scaledDown);
-        mUIDataValid = true;
+        return SCROLL_TYPE_WORKSPACE;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index d1bbd23..605c83c 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.uioverrides.OverviewState;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
@@ -41,12 +42,16 @@
 import java.util.ArrayList;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.quickstep.TaskView.CURVE_FACTOR;
+import static com.android.quickstep.TaskView.CURVE_INTERPOLATOR;
 
 /**
  * A list of recent tasks.
  */
 public class RecentsView extends PagedView {
 
+    private static final Rect sTempStableInsets = new Rect();
+
     public static final int SCROLL_TYPE_NONE = 0;
     public static final int SCROLL_TYPE_TASK = 1;
     public static final int SCROLL_TYPE_WORKSPACE = 2;
@@ -67,7 +72,7 @@
             for (int i = mFirstTaskIndex; i < getChildCount(); i++) {
                 final TaskView taskView = (TaskView) getChildAt(i);
                 if (taskView.getTask().key.id == taskId) {
-                    taskView.getThumbnail().setThumbnail(snapshot);
+                    taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
                     return;
                 }
             }
@@ -219,19 +224,18 @@
     }
 
     private static Rect getPadding(DeviceProfile profile, Context context) {
-        Rect stableInsets = new Rect();
-        WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
+        WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
         Rect padding = new Rect(profile.workspacePadding);
 
-        float taskWidth = profile.widthPx - stableInsets.left - stableInsets.right;
-        float taskHeight = profile.heightPx - stableInsets.top - stableInsets.bottom;
+        float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
+        float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
 
         float overviewHeight, overviewWidth;
         if (profile.isVerticalBarLayout()) {
             // Use the same padding on both sides for symmetry.
             float availableWidth = taskWidth - 2 * Math.max(padding.left, padding.right);
             float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
-                    - stableInsets.top
+                    - sTempStableInsets.top
                     - profile.heightPx * (1 - OverviewState.getVerticalProgress(profile, context));
 
             float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
@@ -240,18 +244,41 @@
 
         } else {
             overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
-                    - stableInsets.top;
+                    - sTempStableInsets.top;
             overviewWidth = taskWidth * overviewHeight / taskHeight;
         }
 
-        padding.bottom = profile.availableHeightPx - padding.top - stableInsets.top
+        padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
                 - Math.round(overviewHeight);
         padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
         return padding;
     }
 
-    public static void getPageRect(Launcher launcher, Rect outRect) {
-        getPageRect(launcher.getDeviceProfile(), launcher, outRect);
+    /**
+     * Sets the {@param outRect} to match the position of the first tile such that it is scaled
+     * down to match the 2nd taskView.
+     * @return returns the factor which determines the scaling factor for the second task.
+     */
+    public static float getScaledDownPageRect(DeviceProfile dp, Context context, Rect outRect) {
+        getPageRect(dp, context, outRect);
+
+        int pageSpacing = context.getResources()
+                .getDimensionPixelSize(R.dimen.recents_page_spacing);
+        float halfScreenWidth = dp.widthPx * 0.5f;
+        float halfPageWidth = outRect.width() * 0.5f;
+        float pageCenter = outRect.right + pageSpacing + halfPageWidth;
+        float distanceFromCenter = Math.abs(halfScreenWidth - pageCenter);
+        float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+        float linearInterpolation = Math.min(1, distanceFromCenter / distanceToReachEdge);
+
+        float scale = 1 - CURVE_INTERPOLATOR.getInterpolation(linearInterpolation) * CURVE_FACTOR;
+
+        int topMargin = context.getResources()
+                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+        outRect.top -= topMargin;
+        Utilities.scaleRectAboutCenter(outRect, scale);
+        outRect.top += (int) (scale * topMargin);
+        return linearInterpolation;
     }
 
     public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
@@ -393,5 +420,7 @@
         public int halfPageWidth;
         public float distanceFromScreenCenter;
         public float linearInterpolation;
+
+        public float prevPageExtraWidth;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 473681f..87dec67 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -22,35 +22,43 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ComposeShader;
 import android.graphics.LightingColorFilter;
+import android.graphics.LinearGradient;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.util.AttributeSet;
-import android.widget.FrameLayout;
+import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
  * A task in the Recents view.
  */
-public class TaskThumbnailView extends FrameLayout {
+public class TaskThumbnailView extends View {
+
+    private static final LightingColorFilter[] sDimFilterCache = new LightingColorFilter[256];
+
+    private final float mCornerRadius;
+    private final float mFadeLength;
+
+    private final Paint mPaint = new Paint();
+
+    private final Matrix mMatrix = new Matrix();
+    private final Rect mThumbnailRect = new Rect();
 
     private ThumbnailData mThumbnailData;
-
-    private Rect mThumbnailRect = new Rect();
-    private float mThumbnailScale;
-
-    private Matrix mMatrix = new Matrix();
-    private Paint mDrawPaint = new Paint();
-    protected Paint mBgFillPaint = new Paint();
     protected BitmapShader mBitmapShader;
 
+    private float mThumbnailScale;
     private float mDimAlpha = 1f;
-    private LightingColorFilter mLightingColorFilter = new LightingColorFilter(Color.WHITE, 0);
 
     public TaskThumbnailView(Context context) {
         this(context, null);
@@ -62,32 +70,34 @@
 
     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        setWillNotDraw(false);
-        setClipToOutline(true);
+        mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
+        mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
     }
 
     /**
      * Updates this thumbnail.
      */
-    public void setThumbnail(ThumbnailData thumbnailData) {
+    public void setThumbnail(Task task, ThumbnailData thumbnailData) {
+        mPaint.setColor(task == null ? Color.BLACK : task.colorBackground | 0xFF000000);
+
         if (thumbnailData != null && thumbnailData.thumbnail != null) {
             Bitmap bm = thumbnailData.thumbnail;
             bm.prepareToDraw();
             mThumbnailScale = thumbnailData.scale;
             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-            mDrawPaint.setShader(mBitmapShader);
+            mPaint.setShader(mBitmapShader);
             mThumbnailRect.set(0, 0,
                     bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right,
                     bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom);
             mThumbnailData = thumbnailData;
             updateThumbnailMatrix();
-            updateThumbnailPaintFilter();
         } else {
             mBitmapShader = null;
-            mDrawPaint.setShader(null);
-            mThumbnailRect.setEmpty();
             mThumbnailData = null;
+            mPaint.setShader(null);
+            mThumbnailRect.setEmpty();
         }
+        updateThumbnailPaintFilter();
     }
 
     /**
@@ -100,43 +110,17 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        int viewWidth = getMeasuredWidth();
-        int viewHeight = getMeasuredHeight();
-        int thumbnailWidth = Math.min(viewWidth,
-                (int) (mThumbnailRect.width() * mThumbnailScale));
-        int thumbnailHeight = Math.min(viewHeight,
-                (int) (mThumbnailRect.height() * mThumbnailScale));
-
-        if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
-            // Draw the background, there will be some small overdraw with the thumbnail
-            if (thumbnailWidth < viewWidth) {
-                // Portrait thumbnail on a landscape task view
-                canvas.drawRect(Math.max(0, thumbnailWidth), 0, viewWidth, viewHeight,
-                        mBgFillPaint);
-            }
-            if (thumbnailHeight < viewHeight) {
-                // Landscape thumbnail on a portrait task view
-                canvas.drawRect(0, Math.max(0, thumbnailHeight), viewWidth, viewHeight,
-                        mBgFillPaint);
-            }
-
-            // Draw the thumbnail
-            canvas.drawRect(0, 0, thumbnailWidth, thumbnailHeight, mDrawPaint);
-        } else {
-            canvas.drawRect(0, 0, viewWidth, viewHeight, mBgFillPaint);
-        }
+        canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+                mCornerRadius, mCornerRadius, mPaint);
     }
 
     private void updateThumbnailPaintFilter() {
         int mul = (int) (mDimAlpha * 255);
         if (mBitmapShader != null) {
-            mLightingColorFilter = new LightingColorFilter(Color.argb(255, mul, mul, mul), 0);
-            mDrawPaint.setColorFilter(mLightingColorFilter);
-            mDrawPaint.setColor(0xFFffffff);
-            mBgFillPaint.setColorFilter(mLightingColorFilter);
+            mPaint.setColorFilter(getLightingColorFilter(mul));
         } else {
-            mDrawPaint.setColorFilter(null);
-            mDrawPaint.setColor(Color.argb(255, mul, mul, mul));
+            mPaint.setColorFilter(null);
+            mPaint.setColor(Color.argb(255, mul, mul, mul));
         }
         invalidate();
     }
@@ -170,6 +154,26 @@
             mMatrix.setTranslate(-mThumbnailData.insets.left, -mThumbnailData.insets.top);
             mMatrix.postScale(mThumbnailScale, mThumbnailScale);
             mBitmapShader.setLocalMatrix(mMatrix);
+
+            float bitmapHeight = Math.max(mThumbnailRect.height() * mThumbnailScale, 0);
+            Shader shader = mBitmapShader;
+            if (bitmapHeight < getMeasuredHeight()) {
+                int color = mPaint.getColor();
+                LinearGradient fade = new LinearGradient(
+                        0, bitmapHeight - mFadeLength, 0, bitmapHeight,
+                        color & 0x00FFFFFF, color, Shader.TileMode.CLAMP);
+                shader = new ComposeShader(fade, shader, Mode.DST_OVER);
+            }
+
+            float bitmapWidth = Math.max(mThumbnailRect.width() * mThumbnailScale, 0);
+            if (bitmapWidth < getMeasuredWidth()) {
+                int color = mPaint.getColor();
+                LinearGradient fade = new LinearGradient(
+                        bitmapWidth - mFadeLength, 0, bitmapWidth, 0,
+                        color & 0x00FFFFFF, color, Shader.TileMode.CLAMP);
+                shader = new ComposeShader(fade, shader, Mode.DST_OVER);
+            }
+            mPaint.setShader(shader);
         }
         invalidate();
     }
@@ -179,4 +183,17 @@
         super.onSizeChanged(w, h, oldw, oldh);
         updateThumbnailMatrix();
     }
+
+    private static LightingColorFilter getLightingColorFilter(int dimColor) {
+        if (dimColor < 0) {
+            dimColor = 0;
+        } else if (dimColor > 255) {
+            dimColor = 255;
+        }
+        if (sDimFilterCache[dimColor] == null) {
+            sDimFilterCache[dimColor] =
+                    new LightingColorFilter(Color.argb(255, dimColor, dimColor, dimColor), 0);
+        }
+        return sDimFilterCache[dimColor];
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskView.java b/quickstep/src/com/android/quickstep/TaskView.java
index 3f733ca..0e999f8 100644
--- a/quickstep/src/com/android/quickstep/TaskView.java
+++ b/quickstep/src/com/android/quickstep/TaskView.java
@@ -16,13 +16,20 @@
 
 package com.android.quickstep;
 
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
+import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
+
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.app.ActivityOptions;
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Property;
+import android.view.View;
+import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
@@ -40,18 +47,15 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_TASK;
-import static com.android.quickstep.RecentsView.SCROLL_TYPE_WORKSPACE;
-
 /**
  * A task in the Recents view.
  */
 public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
 
     /** Designates how "curvy" the carousel is from 0 to 1, where 0 is a straight line. */
-    private static final float CURVE_FACTOR = 0.25f;
+    public static final float CURVE_FACTOR = 0.25f;
     /** A circular curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
-    private static final TimeInterpolator CURVE_INTERPOLATOR
+    public static final TimeInterpolator CURVE_INTERPOLATOR
             = x -> (float) (1 - Math.sqrt(1 - Math.pow(x, 2)));
 
     /**
@@ -90,9 +94,8 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        setOnClickListener((view) -> {
-            launchTask(true /* animate */);
-        });
+        setOnClickListener((view) -> launchTask(true /* animate */));
+        setOutlineProvider(new TaskOutlineProvider(getResources()));
     }
 
     @Override
@@ -155,14 +158,14 @@
 
     @Override
     public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
-        mSnapshotView.setThumbnail(thumbnailData);
+        mSnapshotView.setThumbnail(task, thumbnailData);
         mIconView.setImageDrawable(task.icon);
         mIconView.setOnLongClickListener(icon -> TaskMenuView.showForTask(this));
     }
 
     @Override
     public void onTaskDataUnloaded() {
-        mSnapshotView.setThumbnail(null);
+        mSnapshotView.setThumbnail(null, null);
         mIconView.setImageDrawable(null);
         mIconView.setOnLongClickListener(null);
     }
@@ -206,13 +209,32 @@
             // Make sure that the task cards do not overlap with the workspace card
             float min = scrollState.halfPageWidth * (1 - scale);
             if (scrollState.isRtl) {
-                setTranslationX(Math.min(translation, min));
+                setTranslationX(Math.min(translation, min) - scrollState.prevPageExtraWidth);
             } else {
-                setTranslationX(Math.max(translation, -min));
+                setTranslationX(Math.max(translation, -min) + scrollState.prevPageExtraWidth);
             }
         } else {
             setTranslationX(translation);
         }
+        scrollState.prevPageExtraWidth = 0;
         return SCROLL_TYPE_TASK;
     }
+
+
+    private static final class TaskOutlineProvider extends ViewOutlineProvider {
+
+        private final int mMarginTop;
+        private final float mRadius;
+
+        TaskOutlineProvider(Resources res) {
+            mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
+            mRadius = res.getDimension(R.dimen.task_corner_radius);
+        }
+
+        @Override
+        public void getOutline(View view, Outline outline) {
+            outline.setRoundRect(0, mMarginTop, view.getWidth(),
+                    view.getHeight(), mRadius);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4cfa1b8..c35ffee 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -31,6 +31,8 @@
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -327,11 +329,6 @@
     }
 
     private Bitmap getCurrentTaskSnapshot() {
-        if (mISystemUiProxy == null) {
-            Log.e(TAG, "Never received systemUIProxy");
-            return null;
-        }
-
         TraceHelper.beginSection("TaskSnapshot");
         // TODO: We are using some hardcoded layers for now, to best approximate the activity layers
         Point displaySize = new Point();
@@ -347,9 +344,13 @@
         try {
             return mISystemUiProxy.screenshot(new Rect(), displaySize.x, displaySize.y, 0, 100000,
                     false, rotation).toBitmap();
-        } catch (RemoteException e) {
+        } catch (Exception e) {
             Log.e(TAG, "Error capturing snapshot", e);
-            return null;
+
+            // Return a dummy bitmap
+            Bitmap bitmap = Bitmap.createBitmap(displaySize.x, displaySize.y, Config.RGB_565);
+            bitmap.eraseColor(Color.WHITE);
+            return bitmap;
         } finally {
             TraceHelper.endSection("TaskSnapshot");
         }
diff --git a/res/layout-land/launcher.xml b/res/layout-land/launcher.xml
index 9bd3c67..f26bfbd 100644
--- a/res/layout-land/launcher.xml
+++ b/res/layout-land/launcher.xml
@@ -21,13 +21,13 @@
     android:id="@+id/launcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="?attr/workspaceStatusBarScrim"
     android:fitsSystemWindows="true">
 
     <com.android.launcher3.dragndrop.DragLayer
         android:id="@+id/drag_layer"
         android:clipChildren="false"
         android:clipToPadding="false"
-        android:background="?attr/workspaceStatusBarScrim"
         android:importantForAccessibility="no"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
diff --git a/res/layout-port/launcher.xml b/res/layout-port/launcher.xml
index b678398..cde3bd5 100644
--- a/res/layout-port/launcher.xml
+++ b/res/layout-port/launcher.xml
@@ -22,6 +22,7 @@
     android:id="@+id/launcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="?attr/workspaceStatusBarScrim"
     android:fitsSystemWindows="true">
 
     <com.android.launcher3.dragndrop.DragLayer
@@ -29,7 +30,6 @@
         android:clipChildren="false"
         android:importantForAccessibility="no"
         android:clipToPadding="false"
-        android:background="?attr/workspaceStatusBarScrim"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
diff --git a/res/layout-sw720dp/launcher.xml b/res/layout-sw720dp/launcher.xml
index 7e6c659..fe2f108 100644
--- a/res/layout-sw720dp/launcher.xml
+++ b/res/layout-sw720dp/launcher.xml
@@ -21,6 +21,7 @@
     android:id="@+id/launcher"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="?attr/workspaceStatusBarScrim"
     android:fitsSystemWindows="true">
 
     <com.android.launcher3.dragndrop.DragLayer
@@ -28,7 +29,6 @@
         android:clipChildren="false"
         android:clipToPadding="false"
         android:importantForAccessibility="no"
-        android:background="?attr/workspaceStatusBarScrim"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index eaf695f..5548ca9 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -133,5 +133,10 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"للعمل"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"الملف الشخصي للعمل"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"البحث عن تطبيقات العمل هنا"</string>
-    <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"يحتوي كل تطبيق من تطبيقات العمل على شارة باللون البرتقالي، ويعني هذا أن مؤسستك تريد الحفاظ على أمان التطبيق. ويمكن نقل تطبيقات العمل إلى شاشتك الرئيسية للوصول إليها بسهولة."</string>
+    <!-- no translation found for bottom_work_tab_user_education_body (1485375451542813426) -->
+    <skip />
+    <!-- no translation found for work_mode_on_label (4781128097185272916) -->
+    <skip />
+    <!-- no translation found for work_mode_off_label (3194894777601421047) -->
+    <skip />
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 44a872c..24a653b 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -133,6 +133,10 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"ניתן למצוא כאן את אפליקציות העבודה"</string>
-    <string name="bottom_work_tab_user_education_body" msgid="5834430249581360068">"לכל אפליקציית עבודה יש תג כתום, וזה אומר שאבטחתה מטופלת בידי הארגון. ניתן להעביר אפליקציות עבודה אל מסך דף הבית כדי להקל את הגישה אליהן."</string>
-    <string name="managed_by_your_organisation" msgid="3989423660315876998">"מנוהל בידי הארגון שלך"</string>
+    <!-- no translation found for bottom_work_tab_user_education_body (1485375451542813426) -->
+    <skip />
+    <!-- no translation found for work_mode_on_label (4781128097185272916) -->
+    <skip />
+    <!-- no translation found for work_mode_off_label (3194894777601421047) -->
+    <skip />
 </resources>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e3682b4..38a3044 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -769,12 +769,12 @@
         mAppWidgetHost.setListenIfResumed(true);
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
 
-        if (mShouldFadeInScrim && mDragLayer.getBackground() != null) {
+        if (mShouldFadeInScrim && mLauncherView.getBackground() != null) {
             if (mScrimAnimator != null) {
                 mScrimAnimator.cancel();
             }
-            mDragLayer.getBackground().setAlpha(0);
-            mScrimAnimator = ObjectAnimator.ofInt(mDragLayer.getBackground(),
+            mLauncherView.getBackground().setAlpha(0);
+            mScrimAnimator = ObjectAnimator.ofInt(mLauncherView.getBackground(),
                     LauncherAnimUtils.DRAWABLE_ALPHA, 0, 255);
             mScrimAnimator.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -1857,7 +1857,9 @@
         if (v != null) {
             intent.setSourceBounds(getViewBounds(v));
             // If there is no target package, use the default intent chooser animation
-            launchOptions = hasTargetPackage ? getActivityLaunchOptions(v) : null;
+            launchOptions = hasTargetPackage
+                    ? getActivityLaunchOptions(v, isInMultiWindowModeCompat())
+                    : null;
         } else {
             launchOptions = null;
         }
@@ -1911,8 +1913,7 @@
         }
     }
 
-    @TargetApi(Build.VERSION_CODES.M)
-    public Bundle getActivityLaunchOptions(View v) {
+    public Bundle getDefaultActivityLaunchOptions(View v) {
         if (Utilities.ATLEAST_MARSHMALLOW) {
             int left = 0, top = 0;
             int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
@@ -1927,7 +1928,8 @@
                     height = bounds.height();
                 }
             }
-            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height).toBundle();
+            return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
+                    .toBundle();
         } else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
             // On L devices, we use the device default slide-up transition.
             // On L MR1 devices, we use a custom version of the slide-up transition which
@@ -1938,6 +1940,13 @@
         return null;
     }
 
+    @TargetApi(Build.VERSION_CODES.M)
+    public Bundle getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
+        return useDefaultLaunchOptions
+                ? getDefaultActivityLaunchOptions(v)
+                : UiFactory.getActivityLaunchOptions(this, v);
+    }
+
     public Rect getViewBounds(View v) {
         int[] pos = new int[2];
         v.getLocationOnScreen(pos);
@@ -1950,11 +1959,20 @@
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return mAppLaunchSuccess;
         }
+
+        boolean isShortcut = Utilities.ATLEAST_MARSHMALLOW
+                && (item instanceof ShortcutInfo)
+                && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
+                || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                && !((ShortcutInfo) item).isPromise();
+
         // Only launch using the new animation if the shortcut has not opted out (this is a
         // private contract between launcher and may be ignored in the future).
         boolean useLaunchAnimation = (v != null) &&
                 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
-        Bundle optsBundle = useLaunchAnimation ? getActivityLaunchOptions(v) : null;
+        Bundle optsBundle = useLaunchAnimation
+                ? getActivityLaunchOptions(v, isShortcut || isInMultiWindowModeCompat())
+                : null;
 
         UserHandle user = item == null ? null : item.user;
 
@@ -1964,11 +1982,7 @@
             intent.setSourceBounds(getViewBounds(v));
         }
         try {
-            if (Utilities.ATLEAST_MARSHMALLOW
-                    && (item instanceof ShortcutInfo)
-                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
-                     || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
-                    && !((ShortcutInfo) item).isPromise()) {
+            if (isShortcut) {
                 // Shortcuts need some special checks due to legacy reasons.
                 startShortcutIntentSafely(intent, optsBundle, item);
             } else if (user == null || user.equals(Process.myUserHandle())) {
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index 1a1bec6..18d5234 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -11,6 +11,8 @@
 import android.view.View;
 import android.view.ViewDebug;
 
+import com.android.launcher3.util.Themes;
+
 import static com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ROOT_VIEW;
 
@@ -66,6 +68,7 @@
 
         // Update device profile before notifying th children.
         mLauncher.getDeviceProfile().updateInsets(insets);
+        boolean resetState = !insets.equals(mInsets);
         setInsets(insets);
 
         if (mAlignedView != null) {
@@ -77,10 +80,20 @@
                 mAlignedView.setLayoutParams(lp);
             }
         }
+        if (resetState) {
+            mLauncher.getStateManager().reapplyState();
+        }
 
         return true; // I'll take it from here
     }
 
+    @Override
+    public void setInsets(Rect insets) {
+        super.setInsets(insets);
+        setBackground(insets.top == 0 ? null
+                : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
+    }
+
     public void dispatchInsets() {
         fitSystemWindows(mInsets);
     }
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index bcb6252..8eeeec3 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -146,6 +146,14 @@
         goToState(state, true, delay, null);
     }
 
+    public void reapplyState() {
+        if (mConfig.mCurrentAnimation == null) {
+            for (StateHandler handler : getStateHandlers()) {
+                handler.setState(mState);
+            }
+        }
+    }
+
     private void goToState(LauncherState state, boolean animated, long delay,
             Runnable onCompleteRunnable) {
         if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 4c30853..ad94a6b 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -563,13 +563,21 @@
         computeScrollHelper();
     }
 
+    public int getExpectedHeight() {
+        return getMeasuredHeight();
+    }
+
     public int getNormalChildHeight() {
-        return  getMeasuredHeight() - getPaddingTop() - getPaddingBottom()
+        return  getExpectedHeight() - getPaddingTop() - getPaddingBottom()
                 - mInsets.top - mInsets.bottom;
     }
 
+    public int getExpectedWidth() {
+        return getMeasuredWidth();
+    }
+
     public int getNormalChildWidth() {
-        return  getMeasuredWidth() - getPaddingLeft() - getPaddingRight()
+        return  getExpectedWidth() - getPaddingLeft() - getPaddingRight()
                 - mInsets.left - mInsets.right;
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index de3b09a..c946a44 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3447,6 +3447,18 @@
     }
 
     @Override
+    public int getExpectedHeight() {
+        return getMeasuredHeight() <= 0
+                ? mLauncher.getDeviceProfile().heightPx : getMeasuredHeight();
+    }
+
+    @Override
+    public int getExpectedWidth() {
+        return getMeasuredWidth() <= 0
+                ? mLauncher.getDeviceProfile().widthPx : getMeasuredWidth();
+    }
+
+    @Override
     protected String getPageIndicatorDescription() {
         return getResources().getString(R.string.all_apps_button_label);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 59bdf4b..2e544ec 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -27,7 +27,6 @@
 import android.support.v7.widget.RecyclerView;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -149,9 +148,7 @@
      */
     public void setApps(List<AppInfo> apps) {
         boolean hasWorkProfileApp = hasWorkProfileApp(apps);
-        if (mUsingTabs != hasWorkProfileApp) {
-            rebindAdapters(hasWorkProfileApp);
-        }
+        rebindAdapters(hasWorkProfileApp);
         mComponentToAppMap.clear();
         addOrUpdateApps(apps);
     }
@@ -165,6 +162,7 @@
         }
         onAppsUpdated();
         mSearchUiManager.refreshSearchResult();
+        mHeader.onAppsUpdated();
     }
 
     /**
@@ -261,7 +259,7 @@
         });
 
         mHeader = findViewById(R.id.all_apps_header);
-        rebindAdapters(mUsingTabs);
+        rebindAdapters(mUsingTabs, true /* force */);
 
         mSearchContainer = findViewById(R.id.search_container_all_apps);
         mSearchUiManager = (SearchUiManager) mSearchContainer;
@@ -395,9 +393,14 @@
     }
 
     private void rebindAdapters(boolean showTabs) {
-        if (showTabs != mUsingTabs) {
-            replaceRVContainer(showTabs);
+        rebindAdapters(showTabs, false /* force */);
+    }
+
+    private void rebindAdapters(boolean showTabs, boolean force) {
+        if (showTabs == mUsingTabs && !force) {
+            return;
         }
+        replaceRVContainer(showTabs);
         mUsingTabs = showTabs;
 
         if (mUsingTabs) {
@@ -539,13 +542,16 @@
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].adapter.setLastSearchQuery(query);
         }
-        boolean hasQuery = !TextUtils.isEmpty(query);
-        if (mUsingTabs && hasQuery) {
+        if (mUsingTabs) {
             mSearchModeWhileUsingTabs = true;
             rebindAdapters(false); // hide tabs
-        } else if (mSearchModeWhileUsingTabs && !hasQuery) {
-            mSearchModeWhileUsingTabs = false;
+        }
+    }
+
+    public void onClearSearchResult() {
+        if (mSearchModeWhileUsingTabs) {
             rebindAdapters(true); // show tabs
+            mSearchModeWhileUsingTabs = false;
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index e4f8ad3..2391768 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -219,6 +219,9 @@
         p.y = getTop() - mCurrentRV.getTop() - mParent.getTop();
     }
 
+    public void onAppsUpdated() {
+        mPredictionRow.onAppsUpdated();
+    }
 }
 
 
diff --git a/src/com/android/launcher3/allapps/PredictionRowView.java b/src/com/android/launcher3/allapps/PredictionRowView.java
index c19d663..267ef3c 100644
--- a/src/com/android/launcher3/allapps/PredictionRowView.java
+++ b/src/com/android/launcher3/allapps/PredictionRowView.java
@@ -109,7 +109,7 @@
     public void setNumAppsPerRow(int numPredictedAppsPerRow) {
         if (mNumPredictedAppsPerRow != numPredictedAppsPerRow) {
             mNumPredictedAppsPerRow = numPredictedAppsPerRow;
-            onAppsUpdated();
+            onPredictionsUpdated();
         }
     }
 
@@ -124,7 +124,7 @@
      * Sets the current set of predicted apps.
      *
      * This can be called before we get the full set of applications, we should merge the results
-     * only in onAppsUpdated() which is idempotent.
+     * only in onPredictionsUpdated() which is idempotent.
      *
      * If the number of predicted apps is the same as the previous list of predicted apps,
      * we can optimize by swapping them in place.
@@ -134,10 +134,10 @@
         mPredictedAppComponents.addAll(apps);
         mPredictedApps.clear();
         mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
-        onAppsUpdated();
+        onPredictionsUpdated();
     }
 
-    private void onAppsUpdated() {
+    private void onPredictionsUpdated() {
         int childCountBefore = getChildCount();
         if (getChildCount() != mNumPredictedAppsPerRow) {
             while (getChildCount() > mNumPredictedAppsPerRow) {
@@ -174,6 +174,24 @@
         }
     }
 
+    /**
+     * Refreshes the app icons in the row view, while preserving the same set of predictions.
+     */
+    public void onAppsUpdated() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (!(child instanceof BubbleTextView)) {
+                continue;
+            }
+            if (i >= mPredictedApps.size()) {
+                break;
+            }
+            BubbleTextView icon = (BubbleTextView) getChildAt(i);
+            icon.reset();
+            icon.applyFromApplicationInfo(mPredictedApps.get(i));
+        }
+    }
+
     private List<AppInfo> processPredictedAppComponents(
             List<ComponentKeyMapper<AppInfo>> components) {
         if (mComponentToAppMap.isEmpty()) {
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index e65a2c4..ed41f13 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -184,6 +184,7 @@
         mSearchQueryBuilder.clear();
         mSearchQueryBuilder.clearSpans();
         Selection.setSelection(mSearchQueryBuilder, 0);
+        mAppsView.onClearSearchResult();
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 8826e64..f3a3539 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -42,6 +42,8 @@
 
     public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
 
+    public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
+
     /**
      * Inversion of zInterpolate, compounded with an ease-out.
      */
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index c75e616..93a5679 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -366,13 +366,6 @@
     }
 
     @Override
-    public void setInsets(Rect insets) {
-        super.setInsets(insets);
-        setBackground(insets.top == 0 ? null
-                : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
-    }
-
-    @Override
     public LayoutParams generateLayoutParams(AttributeSet attrs) {
         return new LayoutParams(getContext(), attrs);
     }
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 7b70df7..114b2b8 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -62,6 +62,7 @@
 
     private static NotificationListener sNotificationListenerInstance = null;
     private static NotificationsChangedListener sNotificationsChangedListener;
+    private static StatusBarNotificationsChangedListener sStatusBarNotificationsChangedListener;
     private static boolean sIsConnected;
     private static boolean sIsCreated;
 
@@ -180,10 +181,19 @@
         }
     }
 
+    public static void setStatusBarNotificationsChangedListener
+            (StatusBarNotificationsChangedListener listener) {
+        sStatusBarNotificationsChangedListener = listener;
+    }
+
     public static void removeNotificationsChangedListener() {
         sNotificationsChangedListener = null;
     }
 
+    public static void removeStatusBarNotificationsChangedListener() {
+        sStatusBarNotificationsChangedListener = null;
+    }
+
     @Override
     public void onListenerConnected() {
         super.onListenerConnected();
@@ -205,7 +215,10 @@
     public void onNotificationPosted(final StatusBarNotification sbn) {
         super.onNotificationPosted(sbn);
         mWorkerHandler.obtainMessage(MSG_NOTIFICATION_POSTED, new NotificationPostedMsg(sbn))
-                .sendToTarget();
+            .sendToTarget();
+        if (sStatusBarNotificationsChangedListener != null) {
+            sStatusBarNotificationsChangedListener.onNotificationPosted(sbn);
+        }
     }
 
     /**
@@ -227,10 +240,13 @@
     public void onNotificationRemoved(final StatusBarNotification sbn) {
         super.onNotificationRemoved(sbn);
         Pair<PackageUserKey, NotificationKeyData> packageUserKeyAndNotificationKey
-                = new Pair<>(PackageUserKey.fromNotification(sbn),
-                        NotificationKeyData.fromNotification(sbn));
+            = new Pair<>(PackageUserKey.fromNotification(sbn),
+            NotificationKeyData.fromNotification(sbn));
         mWorkerHandler.obtainMessage(MSG_NOTIFICATION_REMOVED, packageUserKeyAndNotificationKey)
-                .sendToTarget();
+            .sendToTarget();
+        if (sStatusBarNotificationsChangedListener != null) {
+            sStatusBarNotificationsChangedListener.onNotificationRemoved(sbn);
+        }
 
         NotificationGroup notificationGroup = mNotificationGroupMap.get(sbn.getGroupKey());
         if (notificationGroup != null) {
@@ -318,4 +334,9 @@
                 NotificationKeyData notificationKey);
         void onNotificationFullRefresh(List<StatusBarNotification> activeNotifications);
     }
+
+    public interface StatusBarNotificationsChangedListener {
+        void onNotificationPosted(StatusBarNotification sbn);
+        void onNotificationRemoved(StatusBarNotification sbn);
+    }
 }
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 42aa12b..83cbf59 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -82,7 +82,7 @@
                 @Override
                 public void onClick(View view) {
                     Rect sourceBounds = launcher.getViewBounds(view);
-                    Bundle opts = launcher.getActivityLaunchOptions(view);
+                    Bundle opts = launcher.getActivityLaunchOptions(view, true);
                     InfoDropTarget.startDetailsActivityForInfo(itemInfo, launcher, sourceBounds, opts);
                     launcher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                             ControlType.APPINFO_TARGET, view);
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
index 26dd68f..9a09c97 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/OverviewPanel.java
@@ -158,7 +158,7 @@
                 .setPackage(getContext().getPackageName());
         intent.setSourceBounds(mLauncher.getViewBounds(v));
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        getContext().startActivity(intent, mLauncher.getActivityLaunchOptions(v));
+        getContext().startActivity(intent, mLauncher.getActivityLaunchOptions(v, false));
     }
 
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 2ea10c2..9819d71 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -18,12 +18,20 @@
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
+import android.app.ActivityOptions;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
 import android.view.View.AccessibilityDelegate;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.BitmapRenderer;
 import com.android.launcher3.util.TouchController;
 
@@ -58,4 +66,8 @@
     }
 
     public static void resetOverview(Launcher launcher) { }
+
+    public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
+        return launcher.getDefaultActivityLaunchOptions(v);
+    }
 }