Merge "Implement initial transient Taskbar EDU tooltips." into tm-qpr-dev am: 654f714718 am: 4d7e4c4a8e

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20795522

Change-Id: I70307a411262909945f44e7e3b58ff721ccc10d2
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml b/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
new file mode 100644
index 0000000..a20f7da
--- /dev/null
+++ b/quickstep/res/drawable/bg_taskbar_edu_tooltip.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+
+    <corners android:radius="@dimen/dialogCornerRadius" />
+    <solid android:color="?androidprv:attr/colorSurface" />
+</shape>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_edu_features.xml b/quickstep/res/layout/taskbar_edu_features.xml
new file mode 100644
index 0000000..72517b1
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu_features.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/TextAppearance.TaskbarEduTooltip.Title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/taskbar_edu_features"
+        app:layout_constraintEnd_toEndOf="@id/suggestions_animation"
+        app:layout_constraintStart_toStartOf="@id/splitscreen_animation"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.airbnb.lottie.LottieAnimationView
+        android:id="@+id/splitscreen_animation"
+        android:layout_width="@dimen/taskbar_edu_features_lottie_width"
+        android:layout_height="@dimen/taskbar_edu_features_lottie_height"
+        android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
+        android:scaleType="centerCrop"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title"
+        app:lottie_autoPlay="true"
+        app:lottie_loop="true"
+        app:lottie_rawRes="@raw/taskbar_edu_splitscreen_transient" />
+
+    <TextView
+        android:id="@+id/splitscreen_text"
+        style="@style/TextAppearance.TaskbarEduTooltip.Subtext"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/taskbar_edu_splitscreen"
+        app:layout_constraintEnd_toEndOf="@id/splitscreen_animation"
+        app:layout_constraintStart_toStartOf="@id/splitscreen_animation"
+        app:layout_constraintTop_toBottomOf="@id/splitscreen_animation" />
+
+    <com.airbnb.lottie.LottieAnimationView
+        android:id="@+id/suggestions_animation"
+        android:layout_width="@dimen/taskbar_edu_features_lottie_width"
+        android:layout_height="@dimen/taskbar_edu_features_lottie_height"
+        android:layout_marginStart="@dimen/taskbar_edu_features_horizontal_spacing"
+        android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
+        android:scaleType="centerCrop"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/splitscreen_animation"
+        app:layout_constraintTop_toBottomOf="@id/title"
+        app:lottie_autoPlay="true"
+        app:lottie_loop="true"
+        app:lottie_rawRes="@raw/taskbar_edu_suggestions_transient" />
+
+    <TextView
+        android:id="@+id/suggestions_text"
+        style="@style/TextAppearance.TaskbarEduTooltip.Subtext"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/taskbar_edu_suggestions"
+        app:layout_constraintEnd_toEndOf="@id/suggestions_animation"
+        app:layout_constraintStart_toStartOf="@id/suggestions_animation"
+        app:layout_constraintTop_toBottomOf="@id/suggestions_animation" />
+
+    <Button
+        android:id="@+id/done_button"
+        style="@style/TaskbarEdu.Button.Next"
+        android:layout_width="wrap_content"
+        android:layout_height="36dp"
+        android:layout_marginTop="32dp"
+        android:text="@string/taskbar_edu_done"
+        android:textColor="?androidprv:attr/textColorOnAccent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/suggestions_text" />
+</merge>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_edu_swipe.xml b/quickstep/res/layout/taskbar_edu_swipe.xml
new file mode 100644
index 0000000..36a6e28
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu_swipe.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/TextAppearance.TaskbarEduTooltip.Title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="@string/taskbar_edu_stashing"
+        app:layout_constraintEnd_toEndOf="@id/animation"
+        app:layout_constraintStart_toStartOf="@id/animation"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.airbnb.lottie.LottieAnimationView
+        android:id="@+id/animation"
+        android:layout_width="@dimen/taskbar_edu_swipe_lottie_width"
+        android:layout_height="@dimen/taskbar_edu_swipe_lottie_height"
+        android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
+        android:scaleType="centerCrop"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title"
+        app:lottie_autoPlay="true"
+        app:lottie_loop="true"
+        app:lottie_rawRes="@raw/taskbar_edu_stashing_transient" />
+
+</merge>
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_edu_tooltip.xml b/quickstep/res/layout/taskbar_edu_tooltip.xml
new file mode 100644
index 0000000..29f4956
--- /dev/null
+++ b/quickstep/res/layout/taskbar_edu_tooltip.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<com.android.launcher3.taskbar.TaskbarEduTooltip xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom|center"
+    android:layout_marginBottom="16dp"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:fitsSystemWindows="true"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/content"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/bg_taskbar_edu_tooltip"
+        android:elevation="@dimen/taskbar_edu_tooltip_elevation"
+        android:paddingHorizontal="@dimen/taskbar_edu_tooltip_horizontal_margin"
+        android:paddingVertical="@dimen/taskbar_edu_tooltip_vertical_margin" />
+
+    <View
+        android:id="@+id/arrow"
+        android:layout_width="@dimen/popup_arrow_width"
+        android:layout_height="@dimen/popup_arrow_height"
+        android:elevation="@dimen/taskbar_edu_tooltip_elevation" />
+</com.android.launcher3.taskbar.TaskbarEduTooltip>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 2eb4abc..d97e435 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -309,6 +309,18 @@
     <dimen name="taskbar_button_margin_6_5">75dp</dimen>
     <dimen name="taskbar_button_margin_default">48dp</dimen>
 
+    <!-- Taskbar education tooltip -->
+    <dimen name="taskbar_edu_tooltip_elevation">14dp</dimen>
+    <dimen name="taskbar_edu_tooltip_horizontal_margin">32dp</dimen>
+    <dimen name="taskbar_edu_tooltip_vertical_margin">24dp</dimen>
+    <dimen name="taskbar_edu_tooltip_enter_y_delta">20dp</dimen>
+    <dimen name="taskbar_edu_tooltip_exit_y_delta">-10dp</dimen>
+    <dimen name="taskbar_edu_swipe_lottie_width">348dp</dimen>
+    <dimen name="taskbar_edu_swipe_lottie_height">217dp</dimen>
+    <dimen name="taskbar_edu_features_lottie_width">170dp</dimen>
+    <dimen name="taskbar_edu_features_lottie_height">106dp</dimen>
+    <dimen name="taskbar_edu_features_horizontal_spacing">24dp</dimen>
+
     <!-- Recents overview -->
     <dimen name="recents_filter_icon_size">30dp</dimen>
 
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index e691522..761934e 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -238,11 +238,13 @@
     <!-- Accessibility text spoken when the taskbar education panel disappears [CHAR_LIMIT=NONE] -->
     <string name="taskbar_edu_closed">Taskbar education closed</string>
     <!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps at once on their device. [CHAR_LIMIT=60] -->
-    <string name="taskbar_edu_splitscreen">Drag to the side to use 2 apps at once</string>
+    <string name="taskbar_edu_splitscreen">Drag an app to the side to use 2 apps at once</string>
     <!-- Text in dialog that lets a user know how they can show the taskbar on their device. [CHAR_LIMIT=60] -->
     <string name="taskbar_edu_stashing">Short swipe up to show the taskbar</string>
     <!-- Text in dialog that lets a user know how the taskbar suggests apps based on their usage. [CHAR_LIMIT=60] -->
-    <string name="taskbar_edu_suggestions">The taskbar suggests apps based on your routine</string>
+    <string name="taskbar_edu_suggestions">Get app suggestions based on your routine</string>
+    <!-- Title in dialog that shows a user what they can do with the taskbar. [CHAR_LIMIT=60] -->
+    <string name="taskbar_edu_features">Do more with the taskbar</string>
     <!-- Text on button to go to the next screen of a tutorial [CHAR_LIMIT=16] -->
     <string name="taskbar_edu_next">Next</string>
     <!-- Text on button to go to the previous screen of a tutorial [CHAR_LIMIT=16] -->
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index eb75084..5256e29 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -186,4 +186,16 @@
         <item name="android:textSize">24sp</item>
         <item name="android:lines">2</item>
     </style>
+
+    <style name="TextAppearance.TaskbarEduTooltip.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
+        <item name="android:gravity">center_horizontal</item>
+        <item name="android:fontFamily">google-sans</item>
+        <item name="android:textSize">24sp</item>
+    </style>
+
+    <style name="TextAppearance.TaskbarEduTooltip.Subtext" parent="android:TextAppearance.Material.Body1">
+        <item name="android:layout_marginTop">16dp</item>
+        <item name="android:fontFamily">google-sans-text</item>
+        <item name="android:textSize">14sp</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 2521208..258e2bc 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -700,7 +700,7 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
-                if (taskbarController != null && taskbarController.shouldShowEdu()) {
+                if (taskbarController != null && taskbarController.shouldShowEduOnAppLaunch()) {
                     // LAUNCHER_TASKBAR_EDUCATION_SHOWING is set to true here, when the education
                     // flow is about to start, to avoid a race condition with other components
                     // that would show something else to the user as soon as the app is opened.
@@ -716,7 +716,7 @@
                 }
                 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
                 if (taskbarController != null) {
-                    taskbarController.showEdu();
+                    taskbarController.showEduOnAppLaunch();
                 }
                 openingTargets.release();
             }
diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
index 82d1830..48352a2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
@@ -20,6 +20,8 @@
 import android.view.LayoutInflater;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
@@ -32,10 +34,12 @@
 
     protected final LayoutInflater mLayoutInflater;
     private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
+    private final OnboardingPrefs<BaseTaskbarContext> mOnboardingPrefs;
 
     public BaseTaskbarContext(Context windowContext) {
         super(windowContext, Themes.getActivityThemeRes(windowContext));
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+        mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
     }
 
     @Override
@@ -48,6 +52,11 @@
         return mDPChangeListeners;
     }
 
+    @Override
+    public OnboardingPrefs<BaseTaskbarContext> getOnboardingPrefs() {
+        return mOnboardingPrefs;
+    }
+
     /** Callback invoked when a drag is initiated within this context. */
     public abstract void onDragStart();
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 237661e..335482c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -18,6 +18,8 @@
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP;
+import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
 
@@ -259,23 +261,51 @@
     }
 
     /**
-     * Starts the taskbar education flow, if the user hasn't seen it yet.
+     * Starts a Taskbar EDU flow, if the user should see one upon launching an application.
      */
-    public void showEdu() {
-        if (!shouldShowEdu()) {
+    public void showEduOnAppLaunch() {
+        if (!shouldShowEduOnAppLaunch()) {
             return;
         }
-        mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
 
-        mControllers.taskbarEduController.showEdu();
+        // Transient and persistent bottom sheet.
+        if (!ENABLE_TASKBAR_EDU_TOOLTIP.get()) {
+            mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
+            mControllers.taskbarEduController.showEdu();
+            return;
+        }
+
+        // Persistent features EDU tooltip.
+        if (!DisplayController.isTransientTaskbar(mLauncher)) {
+            mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
+            return;
+        }
+
+        // Transient swipe EDU tooltip.
+        mControllers.taskbarEduTooltipController.maybeShowSwipeEdu();
     }
 
     /**
-     * Whether the taskbar education should be shown.
+     * Returns {@code true} if a Taskbar education should be shown on application launch.
      */
-    public boolean shouldShowEdu() {
-        return !Utilities.IS_RUNNING_IN_TEST_HARNESS
-                && !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
+    public boolean shouldShowEduOnAppLaunch() {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            return false;
+        }
+
+        // Transient and persistent bottom sheet.
+        if (!ENABLE_TASKBAR_EDU_TOOLTIP.get()) {
+            return !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
+        }
+
+        // Persistent features EDU tooltip.
+        if (!DisplayController.isTransientTaskbar(mLauncher)) {
+            return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
+                    OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP);
+        }
+
+        // Transient swipe EDU tooltip.
+        return mControllers.taskbarEduTooltipController.getTooltipStep() < TOOLTIP_STEP_FEATURES;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2d464d6..c01e670 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -234,7 +234,8 @@
                 new TaskbarTranslationController(this),
                 isDesktopMode
                         ? new DesktopTaskbarRecentAppsController(this)
-                        : TaskbarRecentAppsController.DEFAULT);
+                        : TaskbarRecentAppsController.DEFAULT,
+                new TaskbarEduTooltipController(this));
     }
 
     public void init(@NonNull TaskbarSharedState sharedState) {
@@ -916,6 +917,7 @@
      */
     public void onSwipeToUnstashTaskbar() {
         mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
+        mControllers.taskbarEduTooltipController.hide();
     }
 
     /** Returns {@code true} if taskbar All Apps is open. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
index 4350e9c..2bfc7dd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendController.java
@@ -39,11 +39,14 @@
     public static final int FLAG_AUTOHIDE_SUSPEND_DRAGGING = 1 << 1;
     // User has touched down but has not lifted finger.
     public static final int FLAG_AUTOHIDE_SUSPEND_TOUCHING = 1 << 2;
+    // Taskbar EDU overlay is open above the Taskbar. */
+    public static final int FLAG_AUTOHIDE_SUSPEND_EDU_OPEN = 1 << 3;
 
     @IntDef(flag = true, value = {
             FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
             FLAG_AUTOHIDE_SUSPEND_DRAGGING,
             FLAG_AUTOHIDE_SUSPEND_TOUCHING,
+            FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AutohideSuspendFlag {}
@@ -102,6 +105,7 @@
                 "FLAG_AUTOHIDE_SUSPEND_FULLSCREEN");
         appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_DRAGGING, "FLAG_AUTOHIDE_SUSPEND_DRAGGING");
         appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_TOUCHING, "FLAG_AUTOHIDE_SUSPEND_TOUCHING");
+        appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_EDU_OPEN, "FLAG_AUTOHIDE_SUSPEND_EDU_OPEN");
         return str.toString();
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 260876b..ea70de4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -58,6 +58,7 @@
     public final TaskbarRecentAppsController taskbarRecentAppsController;
     public final TaskbarTranslationController taskbarTranslationController;
     public final TaskbarOverlayController taskbarOverlayController;
+    public final TaskbarEduTooltipController taskbarEduTooltipController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
     @Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
@@ -101,7 +102,8 @@
             TaskbarInsetsController taskbarInsetsController,
             VoiceInteractionWindowController voiceInteractionWindowController,
             TaskbarTranslationController taskbarTranslationController,
-            TaskbarRecentAppsController taskbarRecentAppsController) {
+            TaskbarRecentAppsController taskbarRecentAppsController,
+            TaskbarEduTooltipController taskbarEduTooltipController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
         this.navButtonController = navButtonController;
@@ -124,6 +126,7 @@
         this.voiceInteractionWindowController = voiceInteractionWindowController;
         this.taskbarTranslationController = taskbarTranslationController;
         this.taskbarRecentAppsController = taskbarRecentAppsController;
+        this.taskbarEduTooltipController = taskbarEduTooltipController;
     }
 
     /**
@@ -155,6 +158,7 @@
         voiceInteractionWindowController.init(this);
         taskbarRecentAppsController.init(this);
         taskbarTranslationController.init(this);
+        taskbarEduTooltipController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -162,7 +166,8 @@
                 taskbarUnfoldAnimationController, taskbarKeyguardController,
                 stashedHandleViewController, taskbarStashController, taskbarEduController,
                 taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
-                voiceInteractionWindowController, taskbarTranslationController
+                voiceInteractionWindowController, taskbarTranslationController,
+                taskbarEduTooltipController
         };
         mBackgroundRendererControllers = new BackgroundRendererController[] {
                 taskbarDragLayerController, taskbarScrimViewController,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
new file mode 100644
index 0000000..7dda73f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 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.taskbar
+
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Interpolator
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.R
+import com.android.launcher3.anim.AnimatorListeners
+import com.android.launcher3.popup.RoundedArrowDrawable
+import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.animation.Interpolators.STANDARD
+
+private const val ENTER_DURATION_MS = 300L
+private const val EXIT_DURATION_MS = 150L
+
+/** Floating tooltip for Taskbar education. */
+class TaskbarEduTooltip
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+) : AbstractFloatingView(context, attrs, defStyleAttr) {
+
+    private val activityContext: ActivityContext = ActivityContext.lookupContext(context)
+
+    private val backgroundColor =
+        Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface)
+
+    private val tooltipCornerRadius = Themes.getDialogCornerRadius(context)
+    private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
+    private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
+    private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius)
+
+    private val enterYDelta = resources.getDimension(R.dimen.taskbar_edu_tooltip_enter_y_delta)
+    private val exitYDelta = resources.getDimension(R.dimen.taskbar_edu_tooltip_exit_y_delta)
+
+    /** Container where the tooltip's body should be inflated. */
+    lateinit var content: ViewGroup
+        private set
+    private lateinit var arrow: View
+
+    /** Callback invoked when the tooltip is being closed. */
+    var onCloseCallback: () -> Unit = {}
+    private var openCloseAnimator: AnimatorSet? = null
+
+    /** Animates the tooltip into view. */
+    fun show() {
+        if (isOpen) {
+            return
+        }
+        mIsOpen = true
+        activityContext.dragLayer.addView(this)
+        openCloseAnimator = createOpenCloseAnimator(isOpening = true).apply { start() }
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+
+        content = findViewById(R.id.content)
+        arrow = findViewById(R.id.arrow)
+        arrow.background =
+            RoundedArrowDrawable(
+                arrowWidth,
+                arrowHeight,
+                arrowPointRadius,
+                tooltipCornerRadius,
+                measuredWidth.toFloat(),
+                measuredHeight.toFloat(),
+                (measuredWidth - arrowWidth) / 2, // arrowOffsetX
+                0f, // arrowOffsetY
+                false, // isPointingUp
+                true, // leftAligned
+                backgroundColor,
+            )
+    }
+
+    override fun handleClose(animate: Boolean) {
+        if (!isOpen) {
+            return
+        }
+
+        onCloseCallback()
+        if (!animate) {
+            return closeComplete()
+        }
+
+        openCloseAnimator?.cancel()
+        openCloseAnimator = createOpenCloseAnimator(isOpening = false)
+        openCloseAnimator?.addListener(AnimatorListeners.forEndCallback(this::closeComplete))
+        openCloseAnimator?.start()
+    }
+
+    override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_EDUCATION_DIALOG != 0
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
+        if (ev?.action == ACTION_DOWN && !activityContext.dragLayer.isEventOverView(this, ev)) {
+            close(true)
+        }
+        return false
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        Settings.Secure.putInt(mContext.contentResolver, LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0)
+    }
+
+    private fun closeComplete() {
+        openCloseAnimator?.cancel()
+        openCloseAnimator = null
+        mIsOpen = false
+        activityContext.dragLayer.removeView(this)
+    }
+
+    private fun createOpenCloseAnimator(isOpening: Boolean): AnimatorSet {
+        val duration: Long
+        val alphaValues: FloatArray
+        val translateYValues: FloatArray
+        val fadeInterpolator: Interpolator
+        val translateYInterpolator: Interpolator
+
+        if (isOpening) {
+            duration = ENTER_DURATION_MS
+            alphaValues = floatArrayOf(0f, 1f)
+            translateYValues = floatArrayOf(enterYDelta, 0f)
+            fadeInterpolator = STANDARD
+            translateYInterpolator = EMPHASIZED_DECELERATE
+        } else {
+            duration = EXIT_DURATION_MS
+            alphaValues = floatArrayOf(1f, 0f)
+            translateYValues = floatArrayOf(0f, exitYDelta)
+            fadeInterpolator = EMPHASIZED_ACCELERATE
+            translateYInterpolator = EMPHASIZED_ACCELERATE
+        }
+
+        val fade =
+            ValueAnimator.ofFloat(*alphaValues).apply {
+                interpolator = fadeInterpolator
+                addUpdateListener {
+                    val alpha = it.animatedValue as Float
+                    content.alpha = alpha
+                    arrow.alpha = alpha
+                }
+            }
+
+        val translateY =
+            ValueAnimator.ofFloat(*translateYValues).apply {
+                interpolator = translateYInterpolator
+                addUpdateListener {
+                    val translationY = it.animatedValue as Float
+                    content.translationY = translationY
+                    arrow.translationY = translationY
+                }
+            }
+
+        return AnimatorSet().apply {
+            this.duration = duration
+            playTogether(fade, translateY)
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
new file mode 100644
index 0000000..817f67d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.taskbar
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.IntDef
+import androidx.annotation.LayoutRes
+import com.android.launcher3.R
+import com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
+import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP
+import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
+import java.io.PrintWriter
+
+/** First EDU step for swiping up to show transient Taskbar. */
+const val TOOLTIP_STEP_SWIPE = 0
+/** Second EDU step for explaining Taskbar functionality when unstashed. */
+const val TOOLTIP_STEP_FEATURES = 1
+/**
+ * EDU is completed.
+ *
+ * This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
+ */
+const val TOOLTIP_STEP_NONE = 2
+
+/** Current step in the tooltip EDU flow. */
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_NONE)
+annotation class TaskbarEduTooltipStep
+
+/** Controls stepping through the Taskbar tooltip EDU. */
+class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
+    LoggableTaskbarController {
+
+    private val isTooltipEnabled = !IS_RUNNING_IN_TEST_HARNESS && ENABLE_TASKBAR_EDU_TOOLTIP.get()
+    private val isOpen: Boolean
+        get() = tooltip?.isOpen ?: false
+
+    private lateinit var controllers: TaskbarControllers
+
+    @TaskbarEduTooltipStep
+    var tooltipStep: Int
+        get() {
+            return activityContext.onboardingPrefs?.getCount(TASKBAR_EDU_TOOLTIP_STEP)
+                ?: TOOLTIP_STEP_NONE
+        }
+        private set(step) {
+            activityContext.onboardingPrefs?.setEventCount(step, TASKBAR_EDU_TOOLTIP_STEP)
+        }
+
+    private var tooltip: TaskbarEduTooltip? = null
+
+    fun init(controllers: TaskbarControllers) {
+        this.controllers = controllers
+    }
+
+    /** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
+    fun maybeShowSwipeEdu() {
+        if (
+            !isTooltipEnabled ||
+                !DisplayController.isTransientTaskbar(activityContext) ||
+                tooltipStep > TOOLTIP_STEP_SWIPE
+        ) {
+            return
+        }
+
+        tooltipStep = TOOLTIP_STEP_FEATURES
+        inflateTooltip(R.layout.taskbar_edu_swipe)
+        tooltip?.show()
+    }
+
+    /**
+     * Shows feature EDU tooltip if this step has not been seen.
+     *
+     * If [TOOLTIP_STEP_SWIPE] has not been seen at this point, the first step is skipped because a
+     * swipe up is necessary to show this step.
+     */
+    fun maybeShowFeaturesEdu() {
+        if (!isTooltipEnabled || tooltipStep > TOOLTIP_STEP_FEATURES) {
+            return
+        }
+
+        tooltipStep = TOOLTIP_STEP_NONE
+        inflateTooltip(R.layout.taskbar_edu_features)
+        tooltip?.apply {
+            findViewById<View>(R.id.done_button)?.setOnClickListener { hide() }
+            if (DisplayController.isTransientTaskbar(activityContext)) {
+                (layoutParams as ViewGroup.MarginLayoutParams).bottomMargin +=
+                    activityContext.deviceProfile.taskbarSize
+            }
+            show()
+        }
+    }
+
+    /** Closes the current [tooltip]. */
+    fun hide() = tooltip?.close(true)
+
+    /** Initializes [tooltip] with content from [contentResId]. */
+    private fun inflateTooltip(@LayoutRes contentResId: Int) {
+        val overlayContext = controllers.taskbarOverlayController.requestWindow()
+        val tooltip =
+            overlayContext.layoutInflater.inflate(
+                R.layout.taskbar_edu_tooltip,
+                overlayContext.dragLayer,
+                false
+            ) as TaskbarEduTooltip
+
+        controllers.taskbarAutohideSuspendController.updateFlag(
+            FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
+            true
+        )
+        tooltip.onCloseCallback = {
+            this.tooltip = null
+            controllers.taskbarAutohideSuspendController.updateFlag(
+                FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
+                false
+            )
+            controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
+        }
+
+        overlayContext.layoutInflater.inflate(contentResId, tooltip.content, true)
+        this.tooltip = tooltip
+    }
+
+    override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
+        pw?.println("$(prefix)TaskbarEduController:")
+        pw?.println("$prefix\tisTooltipEnabled=$isTooltipEnabled")
+        pw?.println("$prefix\tisOpen=$isOpen")
+        pw?.println("$prefix\ttooltipStep=$tooltipStep")
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 61d169f..ef79b8e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -426,6 +426,11 @@
             return;
         }
 
+        if (stash && mControllers.taskbarAutohideSuspendController.isSuspended()) {
+            // Avoid stashing if autohide is currently suspended.
+            return;
+        }
+
         if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
             updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
             applyState();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index 5dba444..586115d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -109,8 +109,12 @@
                 .setStiffness(SpringForce.STIFFNESS_LOW)
                 .build(mTranslationYForSwipe, VALUE);
         mSpringBounce.addListener(forEndCallback(() -> {
-            if (mGestureEnded) {
-                reset();
+            if (!mGestureEnded) {
+                return;
+            }
+            reset();
+            if (mControllers.taskbarStashController.isInAppAndNotStashed()) {
+                mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
             }
         }));
         mSpringBounce.start();
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 38b6dfd..d7a0b03 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -19,7 +19,6 @@
 import android.view.View;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -30,7 +29,6 @@
 import com.android.launcher3.taskbar.TaskbarDragController;
 import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
-import com.android.launcher3.util.OnboardingPrefs;
 
 /**
  * Window context for the taskbar overlays such as All Apps and EDU.
@@ -40,7 +38,6 @@
  */
 public class TaskbarOverlayContext extends BaseTaskbarContext {
     private final TaskbarActivityContext mTaskbarContext;
-    private final OnboardingPrefs<TaskbarOverlayContext> mOnboardingPrefs;
 
     private final TaskbarOverlayController mOverlayController;
     private final TaskbarDragController mDragController;
@@ -59,7 +56,6 @@
         mOverlayController = controllers.taskbarOverlayController;
         mDragController = new TaskbarDragController(this);
         mDragController.init(controllers);
-        mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
         mDragLayer = new TaskbarOverlayDragLayer(this);
 
         TaskbarStashController taskbarStashController = controllers.taskbarStashController;
@@ -100,11 +96,6 @@
     }
 
     @Override
-    public OnboardingPrefs<TaskbarOverlayContext> getOnboardingPrefs() {
-        return mOnboardingPrefs;
-    }
-
-    @Override
     public boolean isBindingItems() {
         return mTaskbarContext.isBindingItems();
     }
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index e8a575e..8a78d8c 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -23,55 +23,35 @@
 import org.mockito.MockitoAnnotations
 
 /**
- * Helper class to extend to get access to all controllers.
- * Gotta be careful of your relationship with this class though, it can be quite... controlling.
+ * Helper class to extend to get access to all controllers. Gotta be careful of your relationship
+ * with this class though, it can be quite... controlling.
  */
 abstract class TaskbarBaseTestCase {
 
-    @Mock
-    lateinit var taskbarActivityContext: TaskbarActivityContext
-    @Mock
-    lateinit var taskbarDragController: TaskbarDragController
-    @Mock
-    lateinit var navButtonController: TaskbarNavButtonController
-    @Mock
-    lateinit var navbarButtonsViewController: NavbarButtonsViewController
-    @Mock
-    lateinit var rotationButtonController: RotationButtonController
-    @Mock
-    lateinit var taskbarDragLayerController: TaskbarDragLayerController
-    @Mock
-    lateinit var taskbarScrimViewController: TaskbarScrimViewController
-    @Mock
-    lateinit var taskbarViewController: TaskbarViewController
-    @Mock
-    lateinit var taskbarUnfoldAnimationController: TaskbarUnfoldAnimationController
-    @Mock
-    lateinit var taskbarKeyguardController: TaskbarKeyguardController
-    @Mock
-    lateinit var stashedHandleViewController: StashedHandleViewController
-    @Mock
-    lateinit var taskbarStashController: TaskbarStashController
-    @Mock
-    lateinit var taskbarEduController: TaskbarEduController
-    @Mock
-    lateinit var taskbarAutohideSuspendController: TaskbarAutohideSuspendController
-    @Mock
-    lateinit var taskbarPopupController: TaskbarPopupController
+    @Mock lateinit var taskbarActivityContext: TaskbarActivityContext
+    @Mock lateinit var taskbarDragController: TaskbarDragController
+    @Mock lateinit var navButtonController: TaskbarNavButtonController
+    @Mock lateinit var navbarButtonsViewController: NavbarButtonsViewController
+    @Mock lateinit var rotationButtonController: RotationButtonController
+    @Mock lateinit var taskbarDragLayerController: TaskbarDragLayerController
+    @Mock lateinit var taskbarScrimViewController: TaskbarScrimViewController
+    @Mock lateinit var taskbarViewController: TaskbarViewController
+    @Mock lateinit var taskbarUnfoldAnimationController: TaskbarUnfoldAnimationController
+    @Mock lateinit var taskbarKeyguardController: TaskbarKeyguardController
+    @Mock lateinit var stashedHandleViewController: StashedHandleViewController
+    @Mock lateinit var taskbarStashController: TaskbarStashController
+    @Mock lateinit var taskbarEduController: TaskbarEduController
+    @Mock lateinit var taskbarAutohideSuspendController: TaskbarAutohideSuspendController
+    @Mock lateinit var taskbarPopupController: TaskbarPopupController
     @Mock
     lateinit var taskbarForceVisibleImmersiveController: TaskbarForceVisibleImmersiveController
-    @Mock
-    lateinit var taskbarAllAppsController: TaskbarAllAppsController
-    @Mock
-    lateinit var taskbarInsetsController: TaskbarInsetsController
-    @Mock
-    lateinit var voiceInteractionWindowController: VoiceInteractionWindowController
-    @Mock
-    lateinit var taskbarRecentAppsController: TaskbarRecentAppsController
-    @Mock
-    lateinit var taskbarTranslationController: TaskbarTranslationController
-    @Mock
-    lateinit var taskbarOverlayController: TaskbarOverlayController
+    @Mock lateinit var taskbarAllAppsController: TaskbarAllAppsController
+    @Mock lateinit var taskbarInsetsController: TaskbarInsetsController
+    @Mock lateinit var voiceInteractionWindowController: VoiceInteractionWindowController
+    @Mock lateinit var taskbarRecentAppsController: TaskbarRecentAppsController
+    @Mock lateinit var taskbarTranslationController: TaskbarTranslationController
+    @Mock lateinit var taskbarOverlayController: TaskbarOverlayController
+    @Mock lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
 
     lateinit var mTaskbarControllers: TaskbarControllers
 
@@ -85,16 +65,31 @@
          * includes that method to allow mocking it.
          */
         MockitoAnnotations.initMocks(this)
-        mTaskbarControllers = TaskbarControllers(
-                taskbarActivityContext, taskbarDragController, navButtonController,
-                navbarButtonsViewController, rotationButtonController, taskbarDragLayerController,
-                taskbarViewController, taskbarScrimViewController, taskbarUnfoldAnimationController,
-                taskbarKeyguardController, stashedHandleViewController, taskbarStashController,
-                taskbarEduController, taskbarAutohideSuspendController, taskbarPopupController,
-                taskbarForceVisibleImmersiveController, taskbarOverlayController,
-                taskbarAllAppsController, taskbarInsetsController,
-                voiceInteractionWindowController, taskbarTranslationController,
-                taskbarRecentAppsController
-        )
+        mTaskbarControllers =
+            TaskbarControllers(
+                taskbarActivityContext,
+                taskbarDragController,
+                navButtonController,
+                navbarButtonsViewController,
+                rotationButtonController,
+                taskbarDragLayerController,
+                taskbarViewController,
+                taskbarScrimViewController,
+                taskbarUnfoldAnimationController,
+                taskbarKeyguardController,
+                stashedHandleViewController,
+                taskbarStashController,
+                taskbarEduController,
+                taskbarAutohideSuspendController,
+                taskbarPopupController,
+                taskbarForceVisibleImmersiveController,
+                taskbarOverlayController,
+                taskbarAllAppsController,
+                taskbarInsetsController,
+                voiceInteractionWindowController,
+                taskbarTranslationController,
+                taskbarRecentAppsController,
+                taskbarEduTooltipController,
+            )
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index f4893c7..c146216 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -44,6 +44,7 @@
     public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen2";
     public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
     public static final String QSB_SEARCH_ONBOARDING_CARD_DISMISSED = "launcher.qsb_edu_dismiss";
+    public static final String TASKBAR_EDU_TOOLTIP_STEP = "launcher.taskbar_edu_tooltip_step";
     // When adding a new key, add it here as well, to be able to reset it from Developer Options.
     public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
             "All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
@@ -51,7 +52,7 @@
                     HOTSEAT_LONGPRESS_TIP_SEEN },
             "Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT,
                     SEARCH_ONBOARDING_COUNT, QSB_SEARCH_ONBOARDING_CARD_DISMISSED},
-            "Taskbar Education", new String[] { TASKBAR_EDU_SEEN },
+            "Taskbar Education", new String[] { TASKBAR_EDU_SEEN, TASKBAR_EDU_TOOLTIP_STEP },
             "All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
     );
 
@@ -76,7 +77,8 @@
             HOTSEAT_DISCOVERY_TIP_COUNT,
             SEARCH_SNACKBAR_COUNT,
             SEARCH_ONBOARDING_COUNT,
-            ALL_APPS_VISITED_COUNT
+            ALL_APPS_VISITED_COUNT,
+            TASKBAR_EDU_TOOLTIP_STEP,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventCountKey {}
@@ -91,6 +93,7 @@
         // This is the sum of all onboarding cards. Currently there is only 1 card shown 3 times.
         maxCounts.put(SEARCH_ONBOARDING_COUNT, 3);
         maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
+        maxCounts.put(TASKBAR_EDU_TOOLTIP_STEP, 2);
         MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
     }