Hybrid hotseat user edu

Starts by showing a notification, which will open a dialog
with options for users to keep their hotseat layout or fully
migrate to hybrid hotseat

Bug:142753423
Test:Manual
Change-Id: I178de612837ec8551f6776fa7c6fb6111bc7431d
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
new file mode 100644
index 0000000..f0e70a8
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<vector android:height="24dp" android:viewportHeight="24"
+    android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@color/hotseat_edu_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
+</vector>
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
new file mode 100644
index 0000000..e3cc549
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<vector android:height="15.53398dp" android:viewportHeight="32"
+    android:viewportWidth="412" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@color/hotseat_edu_background" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
+</vector>
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
new file mode 100644
index 0000000..ee38e3b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.hybridhotseat.HotseatEduDialog xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_gravity="bottom"
+    android:layout_height="wrap_content"
+    android:gravity="bottom"
+    android:orientation="vertical">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:background="@drawable/hotseat_prediction_edu_top" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/hotseat_edu_background"
+        android:orientation="vertical">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="18dp"
+            android:fontFamily="google-sans"
+            android:paddingLeft="@dimen/hotseat_edu_padding"
+            android:paddingRight="@dimen/hotseat_edu_padding"
+            android:text="@string/hotseat_migrate_title"
+            android:textAlignment="center"
+            android:textColor="@android:color/white"
+            android:textSize="20sp" />
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="18dp"
+            android:layout_marginBottom="18dp"
+            android:fontFamily="roboto-medium"
+            android:paddingLeft="@dimen/hotseat_edu_padding"
+            android:paddingRight="@dimen/hotseat_edu_padding"
+            android:text="@string/hotseat_migrate_message"
+            android:textAlignment="center"
+            android:textColor="@android:color/white"
+            android:textSize="16sp" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/hotseat_wrapper"
+            android:orientation="vertical">
+
+            <com.android.launcher3.CellLayout
+                android:id="@+id/sample_prediction"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                launcher:containerType="hotseat" />
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingLeft="@dimen/hotseat_edu_padding"
+                android:paddingTop="8dp"
+                android:paddingRight="@dimen/hotseat_edu_padding">
+
+                <Button
+                    android:id="@+id/turn_predictions_on"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="end"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:text="@string/hotseat_migrate_accept"
+                    android:textAlignment="textEnd"
+                    android:textColor="@android:color/white" />
+
+                <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:id="@+id/no_thanks"
+                    android:text="@string/hotseat_migrate_dismiss"
+                    android:layout_gravity="start"
+                    android:background="?android:attr/selectableItemBackground"
+                    android:textColor="@android:color/white" />
+
+            </FrameLayout>
+        </LinearLayout>
+    </LinearLayout>
+
+</com.android.launcher3.hybridhotseat.HotseatEduDialog>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 7426e30..4fa5684 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -6,4 +6,6 @@
     <color name="all_apps_label_text_dark">#61FFFFFF</color>
     <color name="all_apps_prediction_row_separator">#3c000000</color>
     <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
+
+    <color name="hotseat_edu_background">#f01A73E8</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index ee672d4..c458ec7 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -29,8 +29,7 @@
     <dimen name="swipe_up_y_overshoot">10dp</dimen>
     <dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
 
-    <!-- Predicted icon related -->
-    <dimen name="predicted_icon_background_corner_radius">15dp</dimen>
-    <dimen name="predicted_icon_background_inset">8dp</dimen>
+    <!-- Hybrid hotseat related -->
+    <dimen name="hotseat_edu_padding">24dp</dimen>
 
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index e45eded..06b9f1f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -26,7 +26,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.HotseatPredictionController;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.ItemInfo;
@@ -39,6 +38,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
new file mode 100644
index 0000000..0fd4aac
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.app.NotificationCompat;
+
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ActivityTracker;
+
+import java.util.List;
+
+/**
+ * Controller class for managing user onboaridng flow for hybrid hotseat
+ */
+public class HotseatEduController {
+    public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
+
+    private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding";
+    private static final int ONBOARDING_NOTIFICATION_ID = 7641;
+
+    private final Launcher mLauncher;
+    private List<WorkspaceItemInfo> mPredictedApps;
+    private HotseatEduDialog mActiveDialog;
+
+    private final NotificationManager mNotificationManager;
+    private final Notification mNotification;
+
+    HotseatEduController(Launcher launcher) {
+        mLauncher = launcher;
+        mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
+        createNotificationChannel();
+        mNotification = createNotification();
+    }
+
+    void migrate() {
+        ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
+        int workspacePageCount = mLauncher.getWorkspace().getPageCount();
+        for (int i = 0; i < hotseatVG.getChildCount(); i++) {
+            View child = hotseatVG.getChildAt(i);
+            ItemInfo tag = (ItemInfo) child.getTag();
+            mLauncher.getModelWriter().moveItemInDatabase(tag,
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP, workspacePageCount, tag.screenId,
+                    0);
+        }
+    }
+
+    void removeNotification() {
+        mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
+    }
+
+    void finishOnboarding() {
+        mLauncher.rebindModel();
+        mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
+        removeNotification();
+    }
+
+    void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
+        mPredictedApps = predictedApps;
+        if (!mPredictedApps.isEmpty()
+                && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
+            mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification);
+        }
+    }
+
+    private void createNotificationChannel() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+        CharSequence name = mLauncher.getString(R.string.hotseat_migrate_title);
+        int importance = NotificationManager.IMPORTANCE_LOW;
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name,
+                importance);
+        mNotificationManager.createNotificationChannel(channel);
+    }
+
+    private Notification createNotification() {
+        Intent intent = new Intent(mLauncher.getApplicationContext(), mLauncher.getClass());
+        intent = new NotificationHandler().addToIntent(intent);
+
+        CharSequence name = mLauncher.getString(R.string.hotseat_migrate_prompt_title);
+        String description = mLauncher.getString(R.string.hotseat_migrate_prompt_content);
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(mLauncher,
+                NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(name)
+                .setOngoing(true)
+                .setColor(mLauncher.getColor(R.color.hotseat_edu_background))
+                .setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
+                        PendingIntent.FLAG_CANCEL_CURRENT))
+                .setSmallIcon(R.drawable.hotseat_edu_notification_icon)
+                .setContentText(description);
+        return builder.build();
+
+    }
+
+    void destroy() {
+        removeNotification();
+        if (mActiveDialog != null) {
+            mActiveDialog.setHotseatEduController(null);
+        }
+    }
+
+    void showDialog() {
+        if (mPredictedApps == null || mPredictedApps.isEmpty()) {
+            return;
+        }
+        if (mActiveDialog != null) {
+            mActiveDialog.handleClose(false);
+        }
+        mActiveDialog = HotseatEduDialog.getDialog(mLauncher);
+        mActiveDialog.setHotseatEduController(this);
+        mActiveDialog.show(mPredictedApps);
+    }
+
+    static class NotificationHandler implements
+            ActivityTracker.SchedulerCallback<QuickstepLauncher> {
+        @Override
+        public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
+            activity.getHotseatPredictionController().showEduDialog();
+            return true;
+        }
+    }
+}
+
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
new file mode 100644
index 0000000..4c87945
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2020 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.hybridhotseat;
+
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.views.AbstractSlideInView;
+
+import java.util.List;
+
+/**
+ * User education dialog for hybrid hotseat. Allows user to migrate hotseat items to a new page in
+ * the workspace and shows predictions on the whole hotseat
+ */
+public class HotseatEduDialog extends AbstractSlideInView implements Insettable {
+
+    private static final int DEFAULT_CLOSE_DURATION = 200;
+
+    public static boolean shown = false;
+
+    private final Rect mInsets = new Rect();
+    private View mHotseatWrapper;
+    private CellLayout mSampleHotseat;
+
+    public void setHotseatEduController(HotseatEduController hotseatEduController) {
+        mHotseatEduController = hotseatEduController;
+    }
+
+    private HotseatEduController mHotseatEduController;
+
+    public HotseatEduDialog(Context context, AttributeSet attr) {
+        this(context, attr, 0);
+    }
+
+    public HotseatEduDialog(Context context, AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mContent = this;
+    }
+
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
+        mSampleHotseat = findViewById(R.id.sample_prediction);
+
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        Rect padding = grid.getHotseatLayoutPadding();
+
+        mSampleHotseat.getLayoutParams().height = grid.cellHeightPx;
+        mSampleHotseat.setGridSize(grid.inv.numHotseatIcons, 1);
+        mSampleHotseat.setPadding(padding.left, 0, padding.right, 0);
+
+        Button turnOnBtn = findViewById(R.id.turn_predictions_on);
+        turnOnBtn.setOnClickListener(this::onMigrate);
+
+        Button learnMoreBtn = findViewById(R.id.no_thanks);
+        learnMoreBtn.setOnClickListener(this::onKeepDefault);
+
+    }
+
+    private void onMigrate(View v) {
+        if (mHotseatEduController == null) return;
+        handleClose(true);
+        mHotseatEduController.migrate();
+        mHotseatEduController.finishOnboarding();
+        Toast.makeText(mLauncher, R.string.hotseat_items_migrated, Toast.LENGTH_LONG).show();
+    }
+
+    private void onKeepDefault(View v) {
+        if (mHotseatEduController == null) return;
+        Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
+        mHotseatEduController.finishOnboarding();
+        handleClose(true);
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // Since this is on-boarding popup, it is not a user controlled action.
+    }
+
+    @Override
+    public int getLogContainerType() {
+        return LauncherLogProto.ContainerType.TIP;
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ON_BOARD_POPUP) != 0;
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        int leftInset = insets.left - mInsets.left;
+        int rightInset = insets.right - mInsets.right;
+        int bottomInset = insets.bottom - mInsets.bottom;
+        mInsets.set(insets);
+        setPadding(leftInset, getPaddingTop(), rightInset, 0);
+        mHotseatWrapper.setPadding(mHotseatWrapper.getPaddingLeft(), getPaddingTop(),
+                mHotseatWrapper.getPaddingRight(), bottomInset);
+        mHotseatWrapper.getLayoutParams().height =
+                mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
+    }
+
+
+    private void animateOpen() {
+        if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+            return;
+        }
+        mIsOpen = true;
+        mOpenCloseAnimator.setValues(
+                PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+        mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mOpenCloseAnimator.start();
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        handleClose(true, DEFAULT_CLOSE_DURATION);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        handleClose(false);
+    }
+
+    /**
+     * Opens User education dialog with a list of suggested apps
+     */
+    public void show(List<WorkspaceItemInfo> predictions) {
+        if (getParent() != null
+                || predictions.size() < mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+            return;
+        }
+        mLauncher.getDragLayer().addView(this);
+        animateOpen();
+        for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+            WorkspaceItemInfo info = predictions.get(i);
+            PredictedAppIcon icon = PredictedAppIcon.createIcon(mSampleHotseat, info);
+            icon.setEnabled(false);
+            icon.verifyHighRes();
+            CellLayout.LayoutParams lp = new CellLayout.LayoutParams(i, 0, 1, 1);
+            mSampleHotseat.addViewToCellLayout(icon, i, info.getViewId(), lp, true);
+        }
+    }
+
+    /**
+     * Factory method for HotseatPredictionUserEdu dialog
+     */
+    public static HotseatEduDialog getDialog(Launcher launcher) {
+        LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+        return (HotseatEduDialog) layoutInflater.inflate(
+                R.layout.predicted_hotseat_edu, launcher.getDragLayer(),
+                false);
+
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
rename to quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index b94142a..8f6081b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3;
+package com.android.launcher3.hybridhotseat;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 
@@ -35,6 +35,20 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.appprediction.ComponentKeyMapper;
@@ -93,6 +107,7 @@
     private AllAppsStore mAllAppsStore;
     private AnimatorSet mIconRemoveAnimators;
 
+    private HotseatEduController mHotseatEduController;
 
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
@@ -118,6 +133,23 @@
         }
     }
 
+    /**
+     * Returns whether or not the prediction controller is ready to show predictions
+     */
+    public boolean isReady() {
+        return mLauncher.getSharedPrefs().getBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN,
+                false);
+    }
+
+    /**
+     * Transitions to NORMAL workspace mode and shows edu dialog
+     */
+    public void showEduDialog() {
+        if (mHotseatEduController == null) return;
+        mLauncher.getStateManager().goToState(LauncherState.NORMAL, true,
+                () -> mHotseatEduController.showDialog());
+    }
+
     @Override
     public void onViewAttachedToWindow(View view) {
         mLauncher.getDragController().addDragListener(this);
@@ -133,7 +165,7 @@
     }
 
     private void fillGapsWithPrediction(boolean animate, Runnable callback) {
-        if (mDragObject != null) {
+        if (!isReady() || mDragObject != null) {
             return;
         }
         List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
@@ -234,6 +266,12 @@
         mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
                 this::setPredictedApps);
 
+        if (!isReady()) {
+            if (mHotseatEduController != null) {
+                mHotseatEduController.destroy();
+            }
+            mHotseatEduController = new HotseatEduController(mLauncher);
+        }
         mAppPredictor.requestPredictionUpdate();
     }
 
@@ -244,6 +282,7 @@
         bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup(
                 mLauncher.getWorkspace().getScreenWithId(
                         Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets()));
+
         return bundle;
     }
 
@@ -272,7 +311,11 @@
             mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
         }
         updateDependencies();
-        fillGapsWithPrediction();
+        if (isReady()) {
+            fillGapsWithPrediction();
+        } else if (mHotseatEduController != null) {
+            mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
+        }
     }
 
     private void updateDependencies() {
@@ -466,9 +509,7 @@
     }
 
     @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) {
-
-    }
+    public void reapplyItemInfo(ItemInfoWithIcon info) {}
 
     @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index cae01ae..4b5ba95 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -27,12 +27,12 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.HotseatPredictionController;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 2bc5015..ce87527 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -67,7 +67,30 @@
     <string name="all_apps_prediction_tip">Your predicted apps</string>
 
     <!-- Content description for a close button. [CHAR LIMIT=NONE] -->
-    <string name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
+    <string  name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
+
+    <!-- Hotseat migration notification title -->
+    <string translatable="false" name="hotseat_migrate_prompt_title">Your Hotseat just got smarter</string>
+    <!-- Hotseat migration notification content -->
+    <string translatable="false" name="hotseat_migrate_prompt_content">Tap here to setup and learn more</string>
+    <!-- Hotseat migration wizard title -->
+    <string translatable="false" name="hotseat_migrate_title">Pixel Suggests apps you\'ll need next</string>
+    <!-- Hotseat migration wizard message -->
+    <string translatable="false" name="hotseat_migrate_message">Suggested apps will replace the bottom row of apps. To pin an app, drag it over a suggested app. Touch &amp; hold an app to hide it.</string>
+    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
+    <string translatable="false" name="hotseat_items_migrated">Your hotseat items have been moved to the last page.</string>
+    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
+    <string translatable="false" name="hotseat_no_migration">You can remove items from the hotseat manually to see suggested apps in their spot.</string>
+    <!-- Button text to opt in for fully predicted hotseat -->
+    <string translatable="false" name="hotseat_migrate_accept">Migrate</string>
+    <!-- Button text to dismiss opt in for fully predicted hotseat -->
+    <string translatable="false" name="hotseat_migrate_dismiss">No thanks</string>
+    <!-- Hotseat onboard notification title -->
+    <string translatable="false" name="hotseat_onboard_notification_title">Your hotseat just got smarter</string>
+    <!-- Hotseat onboard notification detail -->
+    <string translatable="false" name="hotseat_onboard_notification_detail">Tap here to set it up</string>
+
+
 
     <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
     <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 03ee707..b89e727 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -49,12 +49,17 @@
         super(context, attrs, defStyle);
     }
 
-    /* Get the orientation specific coordinates given an invariant order in the hotseat. */
-    int getCellXFromOrder(int rank) {
+    /**
+     * Returns orientation specific cell X given invariant order in the hotseat
+     */
+    public int getCellXFromOrder(int rank) {
         return mHasVerticalHotseat ? 0 : rank;
     }
 
-    int getCellYFromOrder(int rank) {
+    /**
+     * Returns orientation specific cell Y given invariant order in the hotseat
+     */
+    public int getCellYFromOrder(int rank) {
         return mHasVerticalHotseat ? (getCountY() - (rank + 1)) : 0;
     }