Implement initial transient Taskbar EDU tooltips.

Since this tooltip looks and behaves differently than the existing EDU
sheet, it has its own view and controller implementations (I also may
have wanted to write some Kotlin).

To keep transient taskbar open while on the second EDU step, another
autohide suspend flag is defined. Additionally, special casing is added
to avoid hiding transient taskbar if autohiding is currently suspended.

Tooltips use the same assets as the bottom sheet for now, and are scaled
down to fit the tooltip dimensions.

Reset `Taskbar Education` in Developer Options to try EDU again.

[Demos]
- First: https://screenshot.googleplex.com/ASBeGvrb2EA5wEF.png
- Second: https://screenshot.googleplex.com/7fnfcTh9bMYezDc.png

Test: Manual
Test: Open app, see swipe-up tooltip.
Test: Swipe up to show transient taskbar, see features tooltip.
Bug: 263157739
Fix: 258460203
Change-Id: I473f5fccbae279db0614763b640da0a120b6b7f7
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 aa9e272..7948ca4 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -699,7 +699,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.
@@ -715,7 +715,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 5a81b2f..8a5b2c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -233,7 +233,8 @@
                 new TaskbarTranslationController(this),
                 isDesktopMode
                         ? new DesktopTaskbarRecentAppsController(this)
-                        : TaskbarRecentAppsController.DEFAULT);
+                        : TaskbarRecentAppsController.DEFAULT,
+                new TaskbarEduTooltipController(this));
     }
 
     public void init(@NonNull TaskbarSharedState sharedState) {
@@ -915,6 +916,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);
     }