Merge "Add IconCache support for deep shortcuts, loads deepshortcut on background thread.  Added a feature flag to toggle on/off this feature." into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
index ba44213..32846dc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -15,7 +15,15 @@
  */
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
 import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -25,14 +33,16 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.appprediction.ComponentKeyMapper;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.appprediction.DynamicItemCache;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -43,73 +53,86 @@
  * pinning of predicted apps and manages replacement of predicted apps with user drag.
  */
 public class HotseatPredictionController implements DragController.DragListener,
-        View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher> {
+        View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
+        InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
+        IconCache.ItemInfoUpdateReceiver {
 
     private static final String TAG = "PredictiveHotseat";
     private static final boolean DEBUG = false;
 
+    private static final String PREDICTION_CLIENT = "hotseat";
 
     private boolean mDragStarted = false;
-    private PredictionUiStateManager mPredictionUiStateManager;
-    private ArrayList<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
+    private int mHotSeatItemsCount;
+
     private Launcher mLauncher;
+    private Hotseat mHotseat;
+
+    private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
+
+    private DynamicItemCache mDynamicItemCache;
+
+    private AppPredictor mAppPredictor;
+    private AllAppsStore mAllAppsStore;
 
     public HotseatPredictionController(Launcher launcher) {
         mLauncher = launcher;
-        mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(mLauncher);
-        if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
-            mLauncher.getHotseat().addOnAttachStateChangeListener(this);
-        }
+        mHotseat = launcher.getHotseat();
+        mAllAppsStore = mLauncher.getAppsView().getAppsStore();
+        mAllAppsStore.addUpdateListener(this);
+        mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false));
+        mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+        launcher.getDeviceProfile().inv.addOnChangeListener(this);
+        mHotseat.addOnAttachStateChangeListener(this);
+        createPredictor();
     }
 
     @Override
     public void onViewAttachedToWindow(View view) {
-        mPredictionUiStateManager.setHotseatPredictionController(this);
         mLauncher.getDragController().addDragListener(this);
     }
 
     @Override
     public void onViewDetachedFromWindow(View view) {
-        mPredictionUiStateManager.setHotseatPredictionController(null);
         mLauncher.getDragController().removeDragListener(this);
     }
 
     /**
-     * sets the list of predicted items. gets called from PredictionUiStateManager
-     */
-    public void setPredictedApps(List<ComponentKeyMapper> apps) {
-        mPredictedApps.clear();
-        mPredictedApps.addAll(mapToWorkspaceItemInfo(apps));
-        fillGapsWithPrediction(false);
-    }
-
-    /**
      * Fills gaps in the hotseat with predictions
      */
     public void fillGapsWithPrediction(boolean animate) {
-        if (!FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() || mDragStarted) {
+        if (mDragStarted) {
             return;
         }
-        removePredictedApps(false);
+        List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
         int predictionIndex = 0;
-        ArrayList<ItemInfo> itemInfos = new ArrayList<>();
-        int cellCount = mLauncher.getWallpaperDeviceProfile().inv.numHotseatIcons;
-        for (int rank = 0; rank < cellCount; rank++) {
-            if (mPredictedApps.size() == predictionIndex) {
-                break;
-            }
-            View child = mLauncher.getHotseat().getChildAt(
-                    mLauncher.getHotseat().getCellXFromOrder(rank),
-                    mLauncher.getHotseat().getCellYFromOrder(rank));
-            if (child != null) {
-                // we already have an item there. skip cell
+        ArrayList<ItemInfo> newItemsToAdd = new ArrayList<>();
+        for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
+            View child = mHotseat.getChildAt(
+                    mHotseat.getCellXFromOrder(rank),
+                    mHotseat.getCellYFromOrder(rank));
+
+            if (child != null && !isPredictedIcon(child)) {
                 continue;
             }
-            WorkspaceItemInfo predictedItem = mPredictedApps.get(predictionIndex++);
+            if (predictedApps.size() <= predictionIndex) {
+                // Remove predicted apps from the past
+                if (isPredictedIcon(child)) {
+                    mHotseat.removeView(child);
+                }
+                continue;
+            }
+
+            WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
+            if (isPredictedIcon(child)) {
+                BubbleTextView icon = (BubbleTextView) child;
+                icon.applyFromWorkspaceItem(predictedItem);
+            } else {
+                newItemsToAdd.add(predictedItem);
+            }
             preparePredictionInfo(predictedItem, rank);
-            itemInfos.add(predictedItem);
         }
-        mLauncher.bindItems(itemInfos, animate);
+        mLauncher.bindItems(newItemsToAdd, animate);
         for (BubbleTextView icon : getPredictedIcons()) {
             icon.verifyHighRes();
             icon.setOnLongClickListener((v) -> {
@@ -119,29 +142,73 @@
         }
     }
 
+    /**
+     * Unregisters callbacks and frees resources
+     */
+    public void destroy() {
+        mAllAppsStore.removeUpdateListener(this);
+        mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
+        mHotseat.removeOnAttachStateChangeListener(this);
+        if (mAppPredictor != null) {
+            mAppPredictor.destroy();
+        }
+    }
+
+    private void createPredictor() {
+        AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
+        if (apm == null) {
+            return;
+        }
+        if (mAppPredictor != null) {
+            mAppPredictor.destroy();
+        }
+        mAppPredictor = apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(mLauncher)
+                        .setUiSurface(PREDICTION_CLIENT)
+                        .setPredictedTargetCount(mHotSeatItemsCount)
+                        .build());
+        mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
+                this::setPredictedApps);
+        mAppPredictor.requestPredictionUpdate();
+    }
+
+    private void setPredictedApps(List<AppTarget> appTargets) {
+        mComponentKeyMappers.clear();
+        for (AppTarget appTarget : appTargets) {
+            ComponentKey key;
+            if (appTarget.getShortcutInfo() != null) {
+                key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
+            } else {
+                key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
+                        appTarget.getClassName()), appTarget.getUser());
+            }
+            mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
+        }
+        updateDependencies();
+        fillGapsWithPrediction(false);
+    }
+
+    private void updateDependencies() {
+        mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
+                mHotSeatItemsCount);
+    }
+
     private void pinPrediction(ItemInfo info) {
-        BubbleTextView icon = (BubbleTextView) mLauncher.getHotseat().getChildAt(
-                mLauncher.getHotseat().getCellXFromOrder(info.rank),
-                mLauncher.getHotseat().getCellYFromOrder(info.rank));
+        BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt(
+                mHotseat.getCellXFromOrder(info.rank),
+                mHotseat.getCellYFromOrder(info.rank));
         if (icon == null) {
             return;
         }
         WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
-        workspaceItemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
         mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
-                workspaceItemInfo.container, workspaceItemInfo.screenId,
+                LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
                 workspaceItemInfo.cellX, workspaceItemInfo.cellY);
-        icon.animate().scaleY(0.8f).scaleX(0.8f).setListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                icon.animate().scaleY(1).scaleX(1);
-            }
-        });
+        ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
         icon.applyFromWorkspaceItem(workspaceItemInfo);
         icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
     }
 
-
     private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
             List<ComponentKeyMapper> components) {
         AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
@@ -163,7 +230,7 @@
                 }
             }
             // Stop at the number of hotseat items
-            if (predictedApps.size() == mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+            if (predictedApps.size() == mHotSeatItemsCount) {
                 break;
             }
         }
@@ -172,12 +239,10 @@
 
     private List<BubbleTextView> getPredictedIcons() {
         List<BubbleTextView> icons = new ArrayList<>();
-        ViewGroup vg = mLauncher.getHotseat().getShortcutsAndWidgets();
+        ViewGroup vg = mHotseat.getShortcutsAndWidgets();
         for (int i = 0; i < vg.getChildCount(); i++) {
             View child = vg.getChildAt(i);
-            if (child instanceof BubbleTextView && child.getTag() instanceof WorkspaceItemInfo
-                    && ((WorkspaceItemInfo) child.getTag()).container
-                    == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            if (isPredictedIcon(child)) {
                 icons.add((BubbleTextView) child);
             }
         }
@@ -191,13 +256,13 @@
                     @Override
                     public void onAnimationSuccess(Animator animator) {
                         if (icon.getParent() != null) {
-                            ((ViewGroup) icon.getParent()).removeView(icon);
+                            mHotseat.removeView(icon);
                         }
                     }
                 });
             } else {
                 if (icon.getParent() != null) {
-                    ((ViewGroup) icon.getParent()).removeView(icon);
+                    mHotseat.removeView(icon);
                 }
             }
         }
@@ -222,7 +287,6 @@
     @Override
     public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
             ItemInfo itemInfo) {
-        if (!FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) return null;
         if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
             return null;
         }
@@ -233,10 +297,27 @@
         itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
         itemInfo.rank = rank;
         itemInfo.cellX = rank;
-        itemInfo.cellY =  LauncherAppState.getIDP(mLauncher).numHotseatIcons - rank - 1;
+        itemInfo.cellY = mHotSeatItemsCount - rank - 1;
         itemInfo.screenId = rank;
     }
 
+    @Override
+    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+        this.mHotSeatItemsCount = profile.numHotseatIcons;
+        createPredictor();
+    }
+
+    @Override
+    public void onAppsUpdated() {
+        updateDependencies();
+        fillGapsWithPrediction(false);
+    }
+
+    @Override
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
+
+    }
+
     private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
 
         private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
@@ -250,4 +331,10 @@
             pinPrediction(mItemInfo);
         }
     }
+
+    private static boolean isPredictedIcon(View view) {
+        return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo
+                && ((WorkspaceItemInfo) view.getTag()).container
+                == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 06580b9..38bb180 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -18,6 +18,7 @@
 import static android.content.pm.PackageManager.MATCH_INSTANT;
 
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
 
 import android.content.Context;
 import android.content.Intent;
@@ -37,8 +38,10 @@
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.AppInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -72,6 +75,7 @@
     private final Handler mUiHandler;
     private final InstantAppResolver mInstantAppResolver;
     private final Runnable mOnUpdateCallback;
+    private final IconCache mIconCache;
 
     private final Map<ShortcutKey, WorkspaceItemInfo> mShortcuts;
     private final Map<String, InstantAppItemInfo> mInstantApps;
@@ -82,6 +86,7 @@
         mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
         mInstantAppResolver = InstantAppResolver.newInstance(context);
         mOnUpdateCallback = onUpdateCallback;
+        mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
 
         mShortcuts = new HashMap<>();
         mInstantApps = new HashMap<>();
@@ -240,4 +245,35 @@
     public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
         return mShortcuts.get(key);
     }
+
+    /**
+     * requests and caches icons for app targets
+     */
+    public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
+            AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
+        List<String> instantAppsToLoad = new ArrayList<>();
+        List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
+        int total = componentKeyMappers.size();
+        for (int i = 0, count = 0; i < total && count < itemCount; i++) {
+            ComponentKeyMapper mapper = componentKeyMappers.get(i);
+            // Update instant apps
+            if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
+                instantAppsToLoad.add(mapper.getPackage());
+                count++;
+            } else if (mapper.getComponentKey() instanceof ShortcutKey) {
+                shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
+                count++;
+            } else {
+                // Reload high res icon
+                AppInfo info = (AppInfo) mapper.getApp(appsStore);
+                if (info != null) {
+                    if (info.usingLowResIcon()) {
+                        mIconCache.updateIconInBackground(callback, info);
+                    }
+                    count++;
+                }
+            }
+        }
+        cacheItems(shortcutsToLoad, instantAppsToLoad);
+    }
 }
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 8fade5c..8338c2e 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
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +18,12 @@
 
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
 
 import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
 
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.HotseatPredictionController;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.ItemInfoWithIcon;
@@ -37,7 +34,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
-import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
@@ -90,8 +86,6 @@
 
     private AllAppsContainerView mAppsView;
 
-    private HotseatPredictionController mHotseatPredictionController;
-
     private PredictionState mPendingState;
     private PredictionState mCurrentState;
 
@@ -153,10 +147,6 @@
         updateDependencies(mCurrentState);
     }
 
-    public void setHotseatPredictionController(HotseatPredictionController controller) {
-        mHotseatPredictionController = controller;
-    }
-
     @Override
     public void reapplyItemInfo(ItemInfoWithIcon info) { }
 
@@ -193,9 +183,6 @@
             mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
                     .setPredictedApps(mCurrentState.apps);
         }
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.setPredictedApps(mCurrentState.apps);
-        }
     }
 
     private void updatePredictionStateAfterCallback() {
@@ -260,33 +247,8 @@
         if (!state.isEnabled || mAppsView == null) {
             return;
         }
-
-        IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
-        List<String> instantAppsToLoad = new ArrayList<>();
-        List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
-        int total = state.apps.size();
-        for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) {
-            ComponentKeyMapper mapper = state.apps.get(i);
-            // Update instant apps
-            if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
-                instantAppsToLoad.add(mapper.getPackage());
-                count++;
-            } else if (mapper.getComponentKey() instanceof ShortcutKey) {
-                shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
-                count++;
-            } else {
-                // Reload high res icon
-                AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore());
-                if (info != null) {
-                    if (info.usingLowResIcon()) {
-                        // TODO: Update icon cache to support null callbacks.
-                        iconCache.updateIconInBackground(this, info);
-                    }
-                    count++;
-                }
-            }
-        }
-        mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad);
+        mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
+                mMaxIconsPerRow);
     }
 
     @Override
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 a6fcf44..eefb7dc 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
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,6 +31,7 @@
 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.popup.SystemShortcut;
 import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
@@ -137,7 +138,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mHotseatPredictionController = new HotseatPredictionController(this);
+        if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+            mHotseatPredictionController = new HotseatPredictionController(this);
+        }
     }
 
     @Override
@@ -165,8 +168,12 @@
 
     @Override
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
-        return Stream.concat(super.getSupportedShortcuts(),
-                Stream.of(mHotseatPredictionController));
+        if (mHotseatPredictionController != null) {
+            return Stream.concat(super.getSupportedShortcuts(),
+                    Stream.of(mHotseatPredictionController));
+        } else {
+            return super.getSupportedShortcuts();
+        }
     }
 
     /**
@@ -187,7 +194,17 @@
     @Override
     public void finishBindingItems(int pageBoundFirst) {
         super.finishBindingItems(pageBoundFirst);
-        mHotseatPredictionController.fillGapsWithPrediction(false);
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.fillGapsWithPrediction(false);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.destroy();
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 1897fb2..ad4a343 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -138,8 +139,13 @@
             if (!recentsView.isRtl()) {
                 pullbackDist = -pullbackDist;
             }
-            Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist);
+            ObjectAnimator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X,
+                    pullbackDist);
             pullback.setInterpolator(PULLBACK_INTERPOLATOR);
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                pullback.addUpdateListener(
+                        valueAnimator -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+            }
             anim.play(pullback);
         } else if (mStartState == ALL_APPS) {
             AnimatorSetBuilder builder = new AnimatorSetBuilder();
@@ -192,6 +198,11 @@
         boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
                 || (velocity < 0 && fling);
         if (success) {
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                RecentsView recentsView = mLauncher.getOverviewPanel();
+                recentsView.switchToScreenshot(null,
+                        () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
+            }
             mLauncher.getStateManager().goToState(mEndState, true,
                     () -> onSwipeInteractionCompleted(mEndState));
             if (mStartState != mEndState) {
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 5158939..fa4c7b9 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager;
 import android.util.Log;
 
+import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -80,7 +81,12 @@
             Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
             return new Statement() {
                 private void assertTrue(String message, boolean condition) {
-                    mLauncher.checkForAnomaly();
+                    if (mLauncher.getDevice().hasObject(By.textStartsWith(""))) {
+                        // The condition above is "screen is not empty". We are not treating
+                        // "Screen is empty" as an anomaly here. It's an acceptable state when
+                        // Launcher just starts under instrumentation.
+                        mLauncher.checkForAnomaly();
+                    }
                     if (!condition) {
                         final AssertionError assertionError = new AssertionError(message);
                         FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ff5f33d..01e9b6e 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -496,7 +496,8 @@
         // Text should be visible everywhere but the hotseat.
         Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag();
         ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null;
-        return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+        return info == null || (info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT
+                && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
     }
 
     public void setTextVisibility(boolean visible) {