Merge "Moving some common functionality to IconLoaderLib" into sc-dev
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 0764bb3..1603321 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -99,62 +99,61 @@
 
     <!-- Dismiss button string for search education view -->
     <string name="search_edu_dismiss">Got it.</string>
-
-    <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
-    <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
-    <!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
-    <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
-    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_swipe_too_far_from_right_edge" translatable="false">Make sure you swipe from the far right edge</string>
-    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_cancelled_right_edge" translatable="false">Make sure you swipe straight to the left and let go</string>
-
-    <!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
-    <string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
-    <!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
-    <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
     <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_swipe_too_far_from_left_edge" translatable="false">Make sure you swipe from the far left edge</string>
+    <string name="back_gesture_feedback_swipe_too_far_from_left_edge">Make sure you swipe from the far-left edge.</string>
     <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_cancelled_left_edge" translatable="false">Make sure you swipe straight to the right and let go</string>
-
+    <string name="back_gesture_feedback_cancelled_left_edge">Make sure you swipe from the left edge to the middle of the screen and let go.</string>
+    <!-- Feedback shown after completing the left back gesture before continuing on to the right edge. [CHAR LIMIT=60] -->
+    <string name="back_gesture_feedback_complete_left_edge">That\'s it! Now try swiping from the right edge.</string>
+    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+    <string name="back_gesture_feedback_swipe_too_far_from_right_edge">Make sure you swipe from the far-right edge.</string>
+    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+    <string name="back_gesture_feedback_cancelled_right_edge">Make sure you swipe from the right edge to the middle of the screen and let go.</string>
+    <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+    <string name="back_gesture_feedback_complete">You completed the go back gesture. Next up, learn how to go Home.</string>
     <!-- Feedback shown during interactive parts of Back gesture tutorial when the gesture is within the nav bar region. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_swipe_in_nav_bar" translatable="false">Make sure you don\'t swipe too close to the bottom of the screen</string>
+    <string name="back_gesture_feedback_swipe_in_nav_bar">Make sure you don\'t swipe too close to the bottom of the screen.</string>
     <!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
-    <string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
-
-    <!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
-    <string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
-    <!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
-    <string name="home_gesture_tutorial_playground_subtitle" translatable="false">Try swiping upward from the bottom edge of the screen</string>
+    <string name="back_gesture_tutorial_confirm_subtitle">To change the sensitivity of the back gesture, go to Settings</string>
     <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
-    <string name="home_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
-    <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
-    <string name="home_gesture_feedback_overview_detected" translatable="false">Make sure you don\'t pause before letting go</string>
-    <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
-    <string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
+    <!-- Introduction title for the Back gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="back_gesture_intro_title">Swipe to go back</string>
+    <!-- Introduction subtitle for the Back gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="back_gesture_intro_subtitle">To go back to the last screen, swipe from the left or right edge to the middle of the screen.</string>
 
-    <!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
-    <string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
-    <!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
-    <string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
+    <string name="home_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
+    <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
+    <string name="home_gesture_feedback_overview_detected">Make sure you don\'t pause before letting go.</string>
+    <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+    <string name="home_gesture_feedback_wrong_swipe_direction">Make sure you swipe straight up.</string>
+    <string name="home_gesture_feedback_complete">You completed the go Home gesture. Next up, learn how to switch apps.</string>
+    <!-- Introduction title for the Home gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="home_gesture_intro_title">Swipe to go home</string>
+    <!-- Introduction subtitle for the Home gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="home_gesture_intro_subtitle">Swipe up from the bottom of your screen. This gesture always takes you to the Home screen.</string>
+
     <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+    <string name="overview_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
     <!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
+    <string name="overview_gesture_feedback_home_detected">Try holding the window for longer before releasing.</string>
     <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
+    <string name="overview_gesture_feedback_wrong_swipe_direction">Make sure you swipe straight up, then pause.</string>
+    <string name="overview_gesture_feedback_complete">You completed the switch apps gesture. You\'re ready to use your phone!</string>
+    <!-- Introduction title for the Overview gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_intro_title">Swipe to switch apps</string>
+    <!-- Introduction subtitle for the Overview gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_intro_subtitle">Swipe up from the bottom of your screen, hold, then release.</string>
 
     <!-- Title shown during interactive part of Assistant gesture tutorial. [CHAR LIMIT=30] -->
     <string name="assistant_gesture_tutorial_playground_title" translatable="false">Tutorial: Assistant</string>
     <!-- Subtitle shown during interactive parts of Assistant gesture tutorial. [CHAR LIMIT=60] -->
     <string name="assistant_gesture_tutorial_playground_subtitle" translatable="false">Try swiping diagonally from a bottom corner of the screen</string>
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture is started too far from the corner. [CHAR LIMIT=100] -->
-    <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen</string>
+    <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen.</string>
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go diagonally enough. [CHAR LIMIT=100] -->
-    <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally</string>
+    <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally.</string>
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go far enough. [CHAR LIMIT=100] -->
-    <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further</string>
+    <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further.</string>
 
     <!-- Title shown in sandbox mode part of gesture tutorial. [CHAR LIMIT=30] -->
     <string name="sandbox_mode_title" translatable="false">Sandbox Mode</string>
@@ -172,11 +171,19 @@
     <string name="sandbox_mode_back_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the left/right edge of the screen</string>
 
     <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
-    <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
-    <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
-    <string name="gesture_tutorial_action_button_label_done" translatable="false">Done</string>
+    <string name="gesture_tutorial_confirm_title">All set</string>
+    <!-- Button text shown on a button on the feedback popup to proceed to the next tutorial step. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_next">Next</string>
+    <!-- Button text shown on a button on the feedback popup to complete the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_done">Done</string>
     <!-- Button text shown on a button to go to Settings. [CHAR LIMIT=14] -->
-    <string name="gesture_tutorial_action_button_label_settings" translatable="false">Settings</string>
+    <string name="gesture_tutorial_action_button_label_settings">Settings</string>
+    <!-- Feedback title to try again. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_try_again">Try again</string>
+    <!-- Feedback title for a successful gesture. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_nice">Nice!</string>
+    <!-- Feedback subtext displaying the current step and the total number of steps for the tutorial. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_step">Tutorial <xliff:g id="current">%1$d</xliff:g>/<xliff:g id="total">%2$d</xliff:g></string>
 
     <!-- ******* Overview ******* -->
     <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
@@ -185,4 +192,14 @@
     <string name="action_screenshot">Screenshot</string>
     <!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
     <string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
+
+    <!-- ******* Skip tutorial dialog ******* -->
+    <!-- Title for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
+    <string name="skip_tutorial_dialog_title">Skip navigation tutorial?</string>
+    <!-- Subtitle for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
+    <string name="skip_tutorial_dialog_subtitle">You can find this later in the Tips app</string>
+    <!-- Button text shown on a button on the tutorial skip dialog to return to the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_cancel">Cancel</string>
+    <!-- Button text shown on a button on the tutorial skip dialog to exit the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_skip">Skip</string>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 6ba7414..085db6d 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -336,6 +336,14 @@
     }
 
     @Override
+    public float getNormalTaskbarScale() {
+        if (mTaskbarController != null) {
+            return mTaskbarController.getTaskbarScaleOnHome();
+        }
+        return super.getNormalTaskbarScale();
+    }
+
+    @Override
     public void onDragLayerHierarchyChanged() {
         onLauncherStateOrFocusChanged();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
similarity index 76%
rename from quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
rename to quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
index 8745a7c..7c54e2d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -20,20 +20,21 @@
 import android.animation.Animator;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Utilities;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
 
 /**
- * Works with TaskbarController to update the TaskbarView's alpha based on LauncherState, whether
- * Launcher is in the foreground, etc.
+ * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
+ * as LauncherState, whether Launcher is in the foreground, etc.
  */
-public class TaskbarVisibilityController {
+public class TaskbarAnimationController {
 
     private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
 
     private final BaseQuickstepLauncher mLauncher;
-    private final TaskbarController.TaskbarVisibilityControllerCallbacks mTaskbarCallbacks;
+    private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
 
     // Background alpha.
     private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
@@ -45,8 +46,12 @@
     private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
             this::updateVisibilityAlpha);
 
-    public TaskbarVisibilityController(BaseQuickstepLauncher launcher,
-            TaskbarController.TaskbarVisibilityControllerCallbacks taskbarCallbacks) {
+    // Scale.
+    private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
+            this::updateScale);
+
+    public TaskbarAnimationController(BaseQuickstepLauncher launcher,
+            TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) {
         mLauncher = launcher;
         mTaskbarCallbacks = taskbarCallbacks;
     }
@@ -72,6 +77,10 @@
         return mTaskbarVisibilityAlphaForLauncherState;
     }
 
+    protected AnimatedFloat getTaskbarScaleForLauncherState() {
+        return mTaskbarScaleForLauncherState;
+    }
+
     protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
         return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
                 .setDuration(duration);
@@ -85,6 +94,7 @@
     private void onTaskbarBackgroundAlphaChanged() {
         mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
         updateVisibilityAlpha();
+        updateScale();
     }
 
     private void updateVisibilityAlpha() {
@@ -101,6 +111,15 @@
         setNavBarButtonAlpha(1f - taskbarAlpha);
     }
 
+    private void updateScale() {
+        // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
+        // assumption being that Taskbar should always be at scale 1f regardless of the current
+        // LauncherState if Launcher is paused.
+        float scale = mTaskbarScaleForLauncherState.value;
+        scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
+        mTaskbarCallbacks.updateTaskbarScale(scale);
+    }
+
     private void setNavBarButtonAlpha(float navBarAlpha) {
         SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index eccc41b..de23ad2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,8 +19,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
@@ -75,7 +73,7 @@
     // Layout width and height of the Taskbar in the default state.
     private final Point mTaskbarSize;
     private final TaskbarStateHandler mTaskbarStateHandler;
-    private final TaskbarVisibilityController mTaskbarVisibilityController;
+    private final TaskbarAnimationController mTaskbarAnimationController;
     private final TaskbarHotseatController mHotseatController;
     private final TaskbarRecentsController mRecentsController;
     private final TaskbarDragController mDragController;
@@ -104,8 +102,8 @@
         mWindowManager = mLauncher.getWindowManager();
         mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
-        mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
-                createTaskbarVisibilityControllerCallbacks());
+        mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
+                createTaskbarAnimationControllerCallbacks());
         mHotseatController = new TaskbarHotseatController(mLauncher,
                 createTaskbarHotseatControllerCallbacks());
         mRecentsController = new TaskbarRecentsController(mLauncher,
@@ -113,8 +111,8 @@
         mDragController = new TaskbarDragController(mLauncher);
     }
 
-    private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() {
-        return new TaskbarVisibilityControllerCallbacks() {
+    private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
+        return new TaskbarAnimationControllerCallbacks() {
             @Override
             public void updateTaskbarBackgroundAlpha(float alpha) {
                 mTaskbarViewInApp.setBackgroundAlpha(alpha);
@@ -125,6 +123,12 @@
                 mTaskbarContainerView.setAlpha(alpha);
                 mTaskbarViewOnHome.setAlpha(alpha);
             }
+
+            @Override
+            public void updateTaskbarScale(float scale) {
+                mTaskbarViewInApp.setScaleX(scale);
+                mTaskbarViewInApp.setScaleY(scale);
+            }
         };
     }
 
@@ -242,12 +246,10 @@
         mTaskbarContainerView.init(mTaskbarViewInApp);
         addToWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
-        mTaskbarVisibilityController.init();
+        mTaskbarAnimationController.init();
         mHotseatController.init();
         mRecentsController.init();
 
-        SCALE_PROPERTY.set(mTaskbarViewInApp, mLauncher.hasBeenResumed()
-                ? getTaskbarScaleOnHome() : 1f);
         updateWhichTaskbarViewIsVisible();
     }
 
@@ -255,7 +257,12 @@
         return new TaskbarStateHandlerCallbacks() {
             @Override
             public AnimatedFloat getAlphaTarget() {
-                return mTaskbarVisibilityController.getTaskbarVisibilityForLauncherState();
+                return mTaskbarAnimationController.getTaskbarVisibilityForLauncherState();
+            }
+
+            @Override
+            public AnimatedFloat getScaleTarget() {
+                return mTaskbarAnimationController.getTaskbarScaleForLauncherState();
             }
         };
     }
@@ -274,7 +281,7 @@
         mTaskbarContainerView.cleanup();
         removeFromWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(null);
-        mTaskbarVisibilityController.cleanup();
+        mTaskbarAnimationController.cleanup();
         mHotseatController.cleanup();
         mRecentsController.cleanup();
     }
@@ -342,12 +349,10 @@
      */
     public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
         PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(0, duration));
+        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
         if (toState != null) {
             mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
         }
-        anim.addFloat(mTaskbarViewInApp, SCALE_PROPERTY, mTaskbarViewInApp.getScaleX(),
-                getTaskbarScaleOnHome(), LINEAR);
 
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -368,8 +373,7 @@
 
     private Animator createAnimToApp(long duration) {
         PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration));
-        anim.addFloat(mTaskbarViewInApp, SCALE_PROPERTY, mTaskbarViewInApp.getScaleX(), 1f, LINEAR);
+        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -390,7 +394,7 @@
      * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
      */
     public void setIsImeVisible(boolean isImeVisible) {
-        mTaskbarVisibilityController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
+        mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
     }
 
     /**
@@ -527,15 +531,17 @@
      */
     protected interface TaskbarStateHandlerCallbacks {
         AnimatedFloat getAlphaTarget();
+        AnimatedFloat getScaleTarget();
     }
 
     /**
-     * Contains methods that TaskbarVisibilityController can call to interface with
+     * Contains methods that TaskbarAnimationController can call to interface with
      * TaskbarController.
      */
-    protected interface TaskbarVisibilityControllerCallbacks {
+    protected interface TaskbarAnimationControllerCallbacks {
         void updateTaskbarBackgroundAlpha(float alpha);
         void updateTaskbarVisibilityAlpha(float alpha);
+        void updateTaskbarScale(float scale);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index 0a3819d..9fc7d99 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -53,8 +53,10 @@
         }
 
         AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
         boolean isTaskbarVisible = (state.getVisibleElements(mLauncher) & TASKBAR) != 0;
         alphaTarget.updateValue(isTaskbarVisible ? 1f : 0f);
+        scaleTarget.updateValue(state.getTaskbarScale(mLauncher));
     }
 
     @Override
@@ -65,7 +67,10 @@
         }
 
         AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
         boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
         animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
+        animation.setFloat(scaleTarget, AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher),
+                LINEAR);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d8a5f9b..43e70a3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -83,6 +83,11 @@
     }
 
     @Override
+    public float getTaskbarScale(Launcher launcher) {
+        return 1f;
+    }
+
+    @Override
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return new PageAlphaProvider(DEACCEL_2) {
             @Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index c91fa9f..1a5f9c2 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -101,6 +101,7 @@
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TransformParams;
@@ -197,7 +198,7 @@
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
     public static final long MAX_SWIPE_DURATION = 350;
-    public static final long MIN_OVERSHOOT_DURATION = 120;
+    public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
 
     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final float SWIPE_DURATION_MULTIPLIER =
@@ -947,7 +948,7 @@
             mInputConsumerProxy.enable();
         }
         if (endTarget == HOME) {
-            duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
+            duration = HOME_DURATION;
         } else if (endTarget == RECENTS) {
             if (mRecentsView != null) {
                 int nearestPage = mRecentsView.getDestinationPage();
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 161e015..a1b7e01 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -36,9 +36,8 @@
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case RIGHT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge;
             case LEFT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge;
+                return R.string.back_gesture_intro_title;
             case BACK_NAVIGATION_COMPLETE:
                 return R.string.gesture_tutorial_confirm_title;
         }
@@ -49,9 +48,8 @@
     Integer getSubtitleStringId() {
         switch (mTutorialType) {
             case RIGHT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge;
             case LEFT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge;
+                return R.string.back_gesture_intro_subtitle;
             case BACK_NAVIGATION_COMPLETE:
                 return R.string.back_gesture_tutorial_confirm_subtitle;
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 95352d1..fbf3a0a 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -38,7 +38,7 @@
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case HOME_NAVIGATION:
-                return R.string.home_gesture_tutorial_playground_title;
+                return R.string.home_gesture_intro_title;
             case HOME_NAVIGATION_COMPLETE:
                 return R.string.gesture_tutorial_confirm_title;
         }
@@ -48,7 +48,7 @@
     @Override
     Integer getSubtitleStringId() {
         if (mTutorialType == TutorialType.HOME_NAVIGATION) {
-            return R.string.home_gesture_tutorial_playground_subtitle;
+            return R.string.home_gesture_intro_subtitle;
         }
         return null;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 45cbd0b..31f26d1 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -39,7 +39,7 @@
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case OVERVIEW_NAVIGATION:
-                return R.string.overview_gesture_tutorial_playground_title;
+                return R.string.overview_gesture_intro_title;
             case OVERVIEW_NAVIGATION_COMPLETE:
                 return R.string.gesture_tutorial_confirm_title;
         }
@@ -49,7 +49,7 @@
     @Override
     Integer getSubtitleStringId() {
         if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) {
-            return R.string.overview_gesture_tutorial_playground_subtitle;
+            return R.string.overview_gesture_intro_subtitle;
         }
         return null;
     }
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index d4ca31f..f68e936 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -60,7 +60,10 @@
 public class StaggeredWorkspaceAnim {
 
     private static final int APP_CLOSE_ROW_START_DELAY_MS = 10;
+    // How long it takes to fade in each staggered row.
     private static final int ALPHA_DURATION_MS = 250;
+    // Should be used for animations running alongside this StaggeredWorkspaceAnim.
+    public static final int DURATION_MS = 250;
 
     private static final float MAX_VELOCITY_PX_PER_S = 22f;
 
@@ -131,15 +134,15 @@
         }
 
         if (animateOverviewScrim) {
-            PendingAnimation pendingAnimation = new PendingAnimation(ALPHA_DURATION_MS);
+            PendingAnimation pendingAnimation = new PendingAnimation(DURATION_MS);
             addScrimAnimationForState(launcher, NORMAL, pendingAnimation);
             mAnimators.play(pendingAnimation.buildAnim());
         }
 
-        addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+        addDepthAnimationForState(launcher, NORMAL, DURATION_MS);
 
         mAnimators.play(launcher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
-                .setDuration(ALPHA_DURATION_MS));
+                .setDuration(DURATION_MS));
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 89c0f66..8785fbc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2763,6 +2763,13 @@
         return new float[] {NO_SCALE, NO_OFFSET};
     }
 
+    /**
+     * @see LauncherState#getTaskbarScale(Launcher)
+     */
+    public float getNormalTaskbarScale() {
+        return 1f;
+    }
+
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
     }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 06bc438..46bce93 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -176,6 +176,10 @@
         return launcher.getNormalOverviewScaleAndOffset();
     }
 
+    public float getTaskbarScale(Launcher launcher) {
+        return launcher.getNormalTaskbarScale();
+    }
+
     public float getOverviewFullscreenProgress() {
         return 0;
     }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 7780894..6f9f0d7 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -40,6 +40,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -224,6 +225,7 @@
                 .map(recommendedWidget -> allWidgetItems.get(
                         new ComponentKey(recommendedWidget.getTargetComponent(),
                                 recommendedWidget.user)))
+                .filter(Objects::nonNull)
                 .collect(Collectors.toList());
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7bfe33c..475a4ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -52,6 +52,7 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
@@ -867,6 +868,16 @@
         return object;
     }
 
+    @Nullable
+    UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
+        try {
+            return container.findObject(selector);
+        } catch (StaleObjectException e) {
+            fail("The container disappeared from screen");
+            return null;
+        }
+    }
+
     @NonNull
     List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
         try {
@@ -1059,6 +1070,11 @@
         final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
         final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
 
+        scrollDownByDistance(container, distance);
+    }
+
+    void scrollDownByDistance(UiObject2 container, int distance) {
+        final Rect containerRect = getVisibleBounds(container);
         final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
         scroll(
                 container,
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index f084913..a3f9ade 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.tapl;
 
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
+import static com.android.launcher3.tapl.LauncherInstrumentation.log;
 
 import android.graphics.Rect;
 
@@ -29,13 +30,13 @@
 import com.android.launcher3.testing.TestProtocol;
 
 import java.util.Collection;
-import java.util.List;
 
 /**
  * All widgets container.
  */
 public final class Widgets extends LauncherInstrumentation.VisibleContainer {
     private static final int FLING_STEPS = 10;
+    private static final int SCROLL_ATTEMPTS = 60;
 
     Widgets(LauncherInstrumentation launcher) {
         super(launcher);
@@ -49,7 +50,7 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to fling forward in widgets")) {
-            LauncherInstrumentation.log("Widgets.flingForward enter");
+            log("Widgets.flingForward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
             mLauncher.scroll(
                     widgetsContainer,
@@ -60,7 +61,7 @@
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
                 verifyActiveContainer();
             }
-            LauncherInstrumentation.log("Widgets.flingForward exit");
+            log("Widgets.flingForward exit");
         }
     }
 
@@ -71,7 +72,7 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to fling backwards in widgets")) {
-            LauncherInstrumentation.log("Widgets.flingBackward enter");
+            log("Widgets.flingBackward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
             mLauncher.scroll(
                     widgetsContainer,
@@ -81,7 +82,7 @@
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
             }
-            LauncherInstrumentation.log("Widgets.flingBackward exit");
+            log("Widgets.flingBackward exit");
         }
     }
 
@@ -101,11 +102,12 @@
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "getting widget " + labelText + " in widgets list")) {
             final UiObject2 searchBar = findSearchBar();
+            final int searchBarHeight = searchBar.getVisibleBounds().height();
             final UiObject2 fullWidgetsPicker = verifyActiveContainer();
             mLauncher.assertTrue("Widgets container didn't become scrollable",
                     fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
 
-            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(searchBar);
+            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
             mLauncher.assertTrue("Can't locate widgets list for the test app: "
                             + mLauncher.getLauncherPackageName(),
                     widgetsContainer != null);
@@ -118,7 +120,8 @@
                 for (UiObject2 row : tableRows) {
                     final Collection<UiObject2> widgetCells = row.getChildren();
                     for (UiObject2 widget : widgetCells) {
-                        final UiObject2 label = widget.findObject(labelSelector);
+                        final UiObject2 label = mLauncher.findObjectInContainer(widget,
+                                labelSelector);
                         if (label == null) {
                             continue;
                         }
@@ -126,28 +129,15 @@
                                 "View is not WidgetCell",
                                 "com.android.launcher3.widget.WidgetCell",
                                 widget.getClassName());
-                        UiObject2 preview = widget.findObject(previewSelector);
-                        mLauncher.assertTrue("Can't find widget preview", preview != null);
-                        Rect previewRect = new Rect(preview.getVisibleBounds());
-                        boolean intersected = searchBar.getVisibleBounds().intersect(previewRect);
-                        if (intersected) {
-                            Rect scrollUp = new Rect(/* left= */ 0, /* top= */0, /* right*/ 0,
-                                    /* bottom= */ searchBar.getVisibleBounds().height());
-                            mLauncher.scroll(
-                                    fullWidgetsPicker,
-                                    Direction.UP,
-                                    scrollUp,
-                                    /* steps= */ 2,
-                                    /* slowDown= */ true);
-                        }
-                        preview = widget.findObject(previewSelector);
+                        UiObject2 preview = mLauncher.waitForObjectInContainer(widget,
+                                previewSelector);
                         return new Widget(mLauncher, preview);
                     }
                 }
 
-                mLauncher.assertTrue("Too many attempts", ++i <= 40);
+                mLauncher.assertTrue("Too many attempts", ++i <= SCROLL_ATTEMPTS);
                 final int scroll = getWidgetsScroll();
-                mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, tableRows, 0);
+                mLauncher.scrollDownByDistance(fullWidgetsPicker, searchBarHeight);
                 final int newScroll = getWidgetsScroll();
                 mLauncher.assertTrue(
                         "Scrolled in a wrong direction in Widgets: from " + scroll + " to "
@@ -164,14 +154,13 @@
                 "widgets_search_bar");
         final UiObject2 searchBarContainer = mLauncher.waitForLauncherObject(
                 searchBarContainerSelector);
-        mLauncher.assertTrue("Can't find a search bar container", searchBarContainer != null);
-        UiObject2 searchBar = searchBarContainer.findObject(searchBarSelector);
-        mLauncher.assertTrue("Can't find a search bar", searchBar != null);
+        UiObject2 searchBar = mLauncher.waitForObjectInContainer(searchBarContainer,
+                searchBarSelector);
         return searchBar;
     }
 
     /** Finds the widgets list of this test app from the collapsed full widgets picker. */
-    private UiObject2 findTestAppWidgetsTableContainer(final UiObject2 searchBar) {
+    private UiObject2 findTestAppWidgetsTableContainer() {
         final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
                 "widgets_list_header");
         final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
@@ -180,50 +169,34 @@
                 "widgets_table");
 
         boolean hasHeaderExpanded = false;
-        for (int i = 0; i < 40; i++) {
+        for (int i = 0; i < SCROLL_ATTEMPTS; i++) {
             UiObject2 fullWidgetsPicker = verifyActiveContainer();
 
-            UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
-            mLauncher.assertTrue("Can't find a widget header", header != null);
+            UiObject2 header = mLauncher.waitForObjectInContainer(fullWidgetsPicker,
+                    headerSelector);
+            int headerHeight = header.getVisibleBounds().height();
 
             // Look for a header that has the test app name.
-            UiObject2 headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
+            UiObject2 headerTitle = mLauncher.findObjectInContainer(fullWidgetsPicker,
+                    targetAppSelector);
             if (headerTitle != null) {
-                Rect headerTitleRect = new Rect(headerTitle.getVisibleBounds());
-                boolean intersected = searchBar.getVisibleBounds().intersect(headerTitleRect);
-                if (intersected) {
-                    Rect scrollUp = new Rect(/* left= */ 0, /* top= */0, /* right*/ 0,
-                            /* bottom= */ searchBar.getVisibleBounds().height());
-                    mLauncher.scroll(
-                            fullWidgetsPicker,
-                            Direction.UP,
-                            scrollUp,
-                            /* steps= */ 2,
-                            /* slowDown= */ true);
-                }
                 // If we find the header and it has not been expanded, let's click it to see the
                 // widgets list.
                 if (!hasHeaderExpanded) {
+                    log("Header has not been expanded. Click to expand.");
                     hasHeaderExpanded = true;
                     mLauncher.clickLauncherObject(headerTitle);
-                    // After clicking the header, the recyclerview has been updated. Let's refresh
-                    // the container UIObject2.
-                    fullWidgetsPicker = verifyActiveContainer();
-                    // Refresh headerTitle because the first instance is stale after
-                    // verifyActiveContainer call.
-                    headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
                 }
 
                 // Look for a widgets list.
-                UiObject2 widgetsContainer = fullWidgetsPicker.findObject(widgetsContainerSelector);
+                UiObject2 widgetsContainer = mLauncher.findObjectInContainer(fullWidgetsPicker,
+                        widgetsContainerSelector);
                 if (widgetsContainer != null) {
+                    log("Widgets container found.");
                     return widgetsContainer;
                 }
-                mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, List.of(headerTitle), 0);
-            } else {
-                mLauncher.scrollToLastVisibleRow(fullWidgetsPicker, fullWidgetsPicker.getChildren(),
-                        0);
             }
+            mLauncher.scrollDownByDistance(fullWidgetsPicker, headerHeight);
         }
 
         return null;