Merge "Fix icon flicker on transition from installing, to installed, to downloading incrementally." into sc-dev
diff --git a/Android.bp b/Android.bp
index b20b307..a720658 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,17 +36,37 @@
     name: "launcher_log_protos_lite",
     srcs: [
         "protos/*.proto",
+        "protos_overrides/*.proto",
     ],
     sdk_version: "current",
     proto: {
         type: "lite",
         local_include_dirs:[
             "protos",
+            "protos_overrides",
         ],
     },
     static_libs: ["libprotobuf-java-lite"],
 }
 
+java_library_static {
+    name: "launcher_quickstep_log_protos_lite",
+    srcs: [
+        "quickstep/protos_overrides/*.proto",
+    ],
+    sdk_version: "current",
+    proto: {
+        type: "lite",
+        local_include_dirs:[
+            "quickstep/protos_overrides",
+        ],
+    },
+    static_libs: [
+      "libprotobuf-java-lite",
+      "launcher_log_protos_lite"
+      ],
+}
+
 java_library {
     name: "LauncherPluginLib",
 
diff --git a/Android.mk b/Android.mk
index ed42039..19ad328 100644
--- a/Android.mk
+++ b/Android.mk
@@ -33,7 +33,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     LauncherPluginLib \
-    launcher_log_protos_lite \
+    launcher_quickstep_log_protos_lite \
     search_ui
 
 LOCAL_SRC_FILES := \
@@ -65,7 +65,7 @@
     $(call all-java-files-under, src_shortcuts_overrides) \
     $(call all-java-files-under, src_ui_overrides) \
     $(call all-java-files-under, ext_tests/src)
-    
+
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/ext_tests/res
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
@@ -129,8 +129,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     SystemUI-statsd \
-    SystemUISharedLib \
-    launcher_log_protos_lite
+    SystemUISharedLib
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
@@ -196,8 +195,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     SystemUI-statsd \
-    SystemUISharedLib \
-    launcher_log_protos_lite
+    SystemUISharedLib
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
diff --git a/build.gradle b/build.gradle
index 28a05d5..a7eef13 100644
--- a/build.gradle
+++ b/build.gradle
@@ -81,7 +81,7 @@
             java.srcDirs = ['src', 'src_plugins']
             manifest.srcFile 'AndroidManifest-common.xml'
             proto {
-                srcDir 'protos/'
+                srcDirs = ['protos/', 'protos_overrides/']
             }
         }
 
@@ -181,4 +181,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index cd229ae..b4c6138 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -18,6 +18,8 @@
 option java_package = "com.android.launcher3.logger";
 option java_outer_classname = "LauncherAtom";
 
+import "launcher_atom_extension.proto";
+
 //
 // ItemInfos
 message ItemInfo {
@@ -55,6 +57,7 @@
     SettingsContainer settings_container = 9;
     PredictedHotseatContainer predicted_hotseat_container = 10;
     TaskSwitcherContainer task_switcher_container = 11;
+    ExtendedContainers extended_containers = 20;
   }
 }
 
diff --git a/protos_overrides/launcher_atom_extension.proto b/protos_overrides/launcher_atom_extension.proto
new file mode 100644
index 0000000..a07daf8
--- /dev/null
+++ b/protos_overrides/launcher_atom_extension.proto
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.logger";
+option java_outer_classname = "LauncherAtomExtensions";
+
+
+// This proto file contains placeholder messages that can be overridden by
+// other Launcher variants.
+// Variants could have its own launcher_atom_extension.proto file(should have
+// same messages declared here but with different implementation); when building
+// variant's apk launcher_atom.proto will reference variant's extension file,
+// essentially overriding this file.
+
+
+// Wrapper message for additional containers used in variants.
+message ExtendedContainers {}
diff --git a/quickstep/protos_overrides/launcher_atom_extension.proto b/quickstep/protos_overrides/launcher_atom_extension.proto
new file mode 100644
index 0000000..2766acf
--- /dev/null
+++ b/quickstep/protos_overrides/launcher_atom_extension.proto
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.logger";
+option java_outer_classname = "LauncherAtomExtensions";
+
+
+// Wrapper message for containers used at the quickstep level.
+// Message name should match with launcher_atom_extension.proto message at
+// the AOSP level.
+message ExtendedContainers {
+
+  oneof Container{
+    DeviceSearchResultContainer device_search_result_container = 1;
+  }
+}
+
+// Represents on-device search result container.
+message DeviceSearchResultContainer{
+}
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
new file mode 100644
index 0000000..6e1aa1e
--- /dev/null
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<View
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/taskbar_divider_thickness"
+    android:layout_height="@dimen/taskbar_divider_height"
+    android:layout_marginStart="@dimen/taskbar_icon_spacing"
+    android:layout_marginEnd="@dimen/taskbar_icon_spacing"
+    android:background="@color/taskbar_divider" />
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 3bc8ddc..54730f1 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -27,4 +27,5 @@
 
     <!-- Taskbar -->
     <color name="taskbar_background">#101010</color>
+    <color name="taskbar_divider">#C0C0C0</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 39cc0b8..0f40775 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -127,4 +127,6 @@
     <dimen name="taskbar_icon_drag_icon_size">54dp</dimen>
     <!-- Note that this applies to both sides of all icons, so visible space is double this. -->
     <dimen name="taskbar_icon_spacing">14dp</dimen>
+    <dimen name="taskbar_divider_thickness">1dp</dimen>
+    <dimen name="taskbar_divider_height">24dp</dimen>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
index c353d7a..f7d5f45 100644
--- a/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
+++ b/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
@@ -49,6 +49,9 @@
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer;
+import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
@@ -142,8 +145,14 @@
 
         SearchActionItemInfo itemInfo = new SearchActionItemInfo(searchAction.getIcon(),
                 searchTarget.getPackageName(), searchTarget.getUserHandle(),
-                searchAction.getTitle()
-        );
+                searchAction.getTitle()) {
+            // Workaround to log ItemInfo with DeviceSearchResultContainer without
+            // updating ItemInfo.container field.
+            @Override
+            public ContainerInfo getContainerInfo() {
+                return buildDeviceSearchResultContainer();
+            }
+        };
         itemInfo.setIntent(searchAction.getIntent());
         itemInfo.setPendingIntent(searchAction.getPendingIntent());
 
@@ -243,7 +252,15 @@
 
     private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
         AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
-        AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle));
+        AppInfo appInfo = new AppInfo(
+                appsStore.getApp(new ComponentKey(componentName, userHandle))) {
+            // Workaround to log ItemInfo with DeviceSearchResultContainer without
+            // updating ItemInfo.container field.
+            @Override
+            public ContainerInfo getContainerInfo() {
+                return buildDeviceSearchResultContainer();
+            }
+        };
 
         if (appInfo == null) {
             setVisibility(GONE);
@@ -253,9 +270,15 @@
         notifyItemInfoChanged(appInfo);
     }
 
-
     private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
-        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()) {
+            // Workaround to log ItemInfo with DeviceSearchResultContainer without
+            // updating ItemInfo.container field.
+            @Override
+            public ContainerInfo getContainerInfo() {
+                return buildDeviceSearchResultContainer();
+            }
+        };
         notifyItemInfoChanged(workspaceItemInfo);
         LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
         MODEL_EXECUTOR.execute(() -> {
@@ -293,4 +316,14 @@
             mOnItemInfoChanged = null;
         }
     }
+
+    private static ContainerInfo buildDeviceSearchResultContainer() {
+        return ContainerInfo.newBuilder().setExtendedContainers(
+                ExtendedContainers
+                        .newBuilder()
+                        .setDeviceSearchResultContainer(
+                                DeviceSearchResultContainer
+                                        .newBuilder()))
+                .build();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 7608645..6a74aac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -23,6 +23,8 @@
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
 import android.animation.Animator;
+import android.app.ActivityOptions;
+import android.content.ComponentName;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.view.Gravity;
@@ -42,8 +44,13 @@
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.quickstep.AnimatedFloat;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView.
  */
@@ -60,11 +67,17 @@
     private final TaskbarStateHandler mTaskbarStateHandler;
     private final TaskbarVisibilityController mTaskbarVisibilityController;
     private final TaskbarHotseatController mHotseatController;
+    private final TaskbarRecentsController mRecentsController;
     private final TaskbarDragController mDragController;
 
     // Initialized in init().
     private WindowManager.LayoutParams mWindowLayoutParams;
 
+    // Contains all loaded Tasks, not yet deduped from Hotseat items.
+    private List<Task> mLatestLoadedRecentTasks;
+    // Contains all loaded Hotseat items.
+    private ItemInfo[] mLatestLoadedHotseatItems;
+
     public TaskbarController(BaseQuickstepLauncher launcher,
             TaskbarContainerView taskbarContainerView) {
         mLauncher = launcher;
@@ -79,6 +92,8 @@
                 createTaskbarVisibilityControllerCallbacks());
         mHotseatController = new TaskbarHotseatController(mLauncher,
                 createTaskbarHotseatControllerCallbacks());
+        mRecentsController = new TaskbarRecentsController(mLauncher,
+                createTaskbarRecentsControllerCallbacks());
         mDragController = new TaskbarDragController(mLauncher);
     }
 
@@ -101,7 +116,16 @@
         return new TaskbarViewCallbacks() {
             @Override
             public View.OnClickListener getItemOnClickListener() {
-                return ItemClickHandler.INSTANCE;
+                return view -> {
+                    Object tag = view.getTag();
+                    if (tag instanceof Task) {
+                        Task task = (Task) tag;
+                        ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
+                                ActivityOptions.makeBasic());
+                    } else {
+                        ItemClickHandler.INSTANCE.onClick(view);
+                    }
+                };
             }
 
             @Override
@@ -116,6 +140,23 @@
             @Override
             public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
                 mTaskbarView.updateHotseatItems(hotseatItemInfos);
+                mLatestLoadedHotseatItems = hotseatItemInfos;
+                dedupeAndUpdateRecentItems();
+            }
+        };
+    }
+
+    private TaskbarRecentsControllerCallbacks createTaskbarRecentsControllerCallbacks() {
+        return new TaskbarRecentsControllerCallbacks() {
+            @Override
+            public void updateRecentItems(ArrayList<Task> recentTasks) {
+                mLatestLoadedRecentTasks = recentTasks;
+                dedupeAndUpdateRecentItems();
+            }
+
+            @Override
+            public void updateRecentTaskAtIndex(int taskIndex, Task task) {
+                mTaskbarView.updateRecentTaskAtIndex(taskIndex, task);
             }
         };
     }
@@ -124,11 +165,13 @@
      * Initializes the Taskbar, including adding it to the screen.
      */
     public void init() {
-        mTaskbarView.init(mHotseatController.getNumHotseatIcons());
+        mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
+                mRecentsController.getNumRecentIcons());
         addToWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
         mTaskbarVisibilityController.init();
         mHotseatController.init();
+        mRecentsController.init();
     }
 
     private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
@@ -149,6 +192,7 @@
         mTaskbarStateHandler.setTaskbarCallbacks(null);
         mTaskbarVisibilityController.cleanup();
         mHotseatController.cleanup();
+        mRecentsController.cleanup();
     }
 
     private void removeFromWindowManager() {
@@ -246,6 +290,52 @@
         return mTaskbarView.isDraggingItem();
     }
 
+    private void dedupeAndUpdateRecentItems() {
+        if (mLatestLoadedRecentTasks == null || mLatestLoadedHotseatItems == null) {
+            return;
+        }
+
+        final int numRecentIcons = mRecentsController.getNumRecentIcons();
+
+        // From most recent to least recently opened.
+        List<Task> dedupedTasksInDescendingOrder = new ArrayList<>();
+        for (int i = mLatestLoadedRecentTasks.size() - 1; i >= 0; i--) {
+            Task task = mLatestLoadedRecentTasks.get(i);
+            boolean isTaskInHotseat = false;
+            for (ItemInfo hotseatItem : mLatestLoadedHotseatItems) {
+                if (hotseatItem == null) {
+                    continue;
+                }
+                ComponentName hotseatActivity = hotseatItem.getTargetComponent();
+                if (hotseatActivity != null && task.key.sourceComponent.getPackageName()
+                        .equals(hotseatActivity.getPackageName())) {
+                    isTaskInHotseat = true;
+                    break;
+                }
+            }
+            if (!isTaskInHotseat) {
+                dedupedTasksInDescendingOrder.add(task);
+                if (dedupedTasksInDescendingOrder.size() == numRecentIcons) {
+                    break;
+                }
+            }
+        }
+
+        // TaskbarView expects an array of all the recent tasks to show, in the order to show them.
+        // So we create an array of the proper size, then fill it in such that the most recent items
+        // are at the end. If there aren't enough elements to fill the array, leave them null.
+        Task[] tasksArray = new Task[numRecentIcons];
+        for (int i = 0; i < tasksArray.length; i++) {
+            Task task = i >= dedupedTasksInDescendingOrder.size()
+                    ? null
+                    : dedupedTasksInDescendingOrder.get(i);
+            tasksArray[tasksArray.length - 1 - i] = task;
+        }
+
+        mTaskbarView.updateRecentTasks(tasksArray);
+        mRecentsController.loadIconsForTasks(tasksArray);
+    }
+
     /**
      * @return Whether the given View is in the same window as Taskbar.
      */
@@ -283,4 +373,12 @@
     protected interface TaskbarHotseatControllerCallbacks {
         void updateHotseatItems(ItemInfo[] hotseatItemInfos);
     }
+
+    /**
+     * Contains methods that TaskbarRecentsController can call to interface with TaskbarController.
+     */
+    protected interface TaskbarRecentsControllerCallbacks {
+        void updateRecentItems(ArrayList<Task> recentTasks);
+        void updateRecentTaskAtIndex(int taskIndex, Task task);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 2318ff9..baec899 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -25,6 +25,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Point;
+import android.os.UserHandle;
 import android.view.DragEvent;
 import android.view.View;
 
@@ -33,6 +34,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ClipDescriptionCompat;
 import com.android.systemui.shared.system.LauncherAppsCompat;
 
@@ -102,6 +104,15 @@
                                 item.getIntent().getComponent(), null, item.user));
             }
             intent.putExtra(Intent.EXTRA_USER, item.user);
+        } else if (tag instanceof Task) {
+            Task task = (Task) tag;
+            clipDescription = new ClipDescription(task.titleDescription,
+                    new String[] {
+                            ClipDescriptionCompat.MIMETYPE_APPLICATION_TASK
+                    });
+            intent = new Intent();
+            intent.putExtra(ClipDescriptionCompat.EXTRA_TASK_ID, task.key.id);
+            intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId));
         }
 
         if (clipDescription != null && intent != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java
new file mode 100644
index 0000000..9d4e000
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentsController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 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 com.android.launcher3.BaseQuickstepLauncher;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.util.CancellableTask;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+
+import java.util.ArrayList;
+
+/**
+ * Works with TaskbarController to update the TaskbarView's Recent items.
+ */
+public class TaskbarRecentsController {
+
+    private final int mNumRecentIcons = 2;
+    private final BaseQuickstepLauncher mLauncher;
+    private final TaskbarController.TaskbarRecentsControllerCallbacks mTaskbarCallbacks;
+    private final RecentsModel mRecentsModel;
+
+    private final TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() {
+        @Override
+        public void onTaskStackChanged() {
+            reloadRecentTasksIfNeeded();
+        }
+    };
+
+    // TODO: add TaskbarVisualsChangedListener as well (for calendar/clock?)
+
+    // Used to keep track of the last requested task list id, so that we do not request to load the
+    // tasks again if we have already requested it and the task list has not changed
+    private int mTaskListChangeId = -1;
+
+    // The current background requests to load the task icons
+    private CancellableTask[] mIconLoadRequests = new CancellableTask[mNumRecentIcons];
+
+    public TaskbarRecentsController(BaseQuickstepLauncher launcher,
+            TaskbarController.TaskbarRecentsControllerCallbacks taskbarCallbacks) {
+        mLauncher = launcher;
+        mTaskbarCallbacks = taskbarCallbacks;
+        mRecentsModel = RecentsModel.INSTANCE.get(mLauncher);
+    }
+
+    protected void init() {
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackChangeListener);
+        reloadRecentTasksIfNeeded();
+    }
+
+    protected void cleanup() {
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
+                mTaskStackChangeListener);
+        cancelAllPendingIconLoadTasks();
+    }
+
+    private void reloadRecentTasksIfNeeded() {
+        if (!mRecentsModel.isTaskListValid(mTaskListChangeId)) {
+            mTaskListChangeId = mRecentsModel.getTasks(this::onRecentTasksChanged);
+        }
+    }
+
+    private void cancelAllPendingIconLoadTasks() {
+        for (int i = 0; i < mIconLoadRequests.length; i++) {
+            if (mIconLoadRequests[i] != null) {
+                mIconLoadRequests[i].cancel();
+            }
+            mIconLoadRequests[i] = null;
+        }
+    }
+
+    private void onRecentTasksChanged(ArrayList<Task> tasks) {
+        mTaskbarCallbacks.updateRecentItems(tasks);
+    }
+
+    /**
+     * For each Task, loads its icon from the cache in the background, then calls
+     * {@link TaskbarController.TaskbarRecentsControllerCallbacks#updateRecentTaskAtIndex}.
+     */
+    protected void loadIconsForTasks(Task[] tasks) {
+        cancelAllPendingIconLoadTasks();
+        for (int i = 0; i < tasks.length; i++) {
+            Task task = tasks[i];
+            if (task == null) {
+                continue;
+            }
+            final int taskIndex = i;
+            mIconLoadRequests[i] = mRecentsModel.getIconCache().updateIconInBackground(
+                    task, updatedTask -> onTaskIconLoaded(task, taskIndex));
+        }
+    }
+
+    private void onTaskIconLoaded(Task task, int taskIndex) {
+        mTaskbarCallbacks.updateRecentTaskAtIndex(taskIndex, task);
+    }
+
+    protected int getNumRecentIcons() {
+        return mNumRecentIcons;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c98f09c..d8f3bb5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -19,6 +19,7 @@
 import android.content.res.Resources;
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.DragEvent;
 import android.view.LayoutInflater;
@@ -35,6 +36,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
@@ -52,6 +54,9 @@
     // Initialized in init().
     private int mHotseatStartIndex;
     private int mHotseatEndIndex;
+    private View mHotseatRecentsDivider;
+    private int mRecentsStartIndex;
+    private int mRecentsEndIndex;
 
     private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
 
@@ -89,10 +94,17 @@
         mControllerCallbacks = taskbarViewCallbacks;
     }
 
-    protected void init(int numHotseatIcons) {
+    protected void init(int numHotseatIcons, int numRecentIcons) {
         mHotseatStartIndex = 0;
         mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
+
+        int dividerIndex = mHotseatEndIndex + 1;
+        mHotseatRecentsDivider = addDivider(dividerIndex);
+
+        mRecentsStartIndex = dividerIndex + 1;
+        mRecentsEndIndex = mRecentsStartIndex + numRecentIcons - 1;
+        updateRecentTasks(new Task[numRecentIcons]);
     }
 
     protected void cleanup() {
@@ -147,6 +159,93 @@
                 hotseatView.setOnLongClickListener(null);
             }
         }
+
+        updateHotseatRecentsDividerVisibility();
+    }
+
+    private View addDivider(int dividerIndex) {
+        View divider = inflate(R.layout.taskbar_divider);
+        addView(divider, dividerIndex);
+        return divider;
+    }
+
+    /**
+     * Inflates/binds the Recents items to show in the Taskbar given their Tasks.
+     */
+    protected void updateRecentTasks(Task[] tasks) {
+        for (int i = 0; i < tasks.length; i++) {
+            Task task = tasks[i];
+            int recentsIndex = mRecentsStartIndex + i;
+            View recentsView = getChildAt(recentsIndex);
+
+            // Inflate empty icon Views.
+            if (recentsView == null) {
+                BubbleTextView btv = (BubbleTextView) inflate(R.layout.taskbar_app_icon);
+                LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize());
+                lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+                recentsView = btv;
+                addView(recentsView, recentsIndex, lp);
+            }
+
+            // Apply the Task, or hide the view if there is none for a given index.
+            if (recentsView instanceof BubbleTextView && task != null) {
+                applyTaskToBubbleTextView((BubbleTextView) recentsView, task);
+                recentsView.setVisibility(VISIBLE);
+                recentsView.setOnClickListener(mControllerCallbacks.getItemOnClickListener());
+                recentsView.setOnLongClickListener(
+                        mControllerCallbacks.getItemOnLongClickListener());
+            } else {
+                recentsView.setVisibility(GONE);
+                recentsView.setOnClickListener(null);
+                recentsView.setOnLongClickListener(null);
+            }
+        }
+
+        updateHotseatRecentsDividerVisibility();
+    }
+
+    private void applyTaskToBubbleTextView(BubbleTextView btv, Task task) {
+        if (task.icon != null) {
+            Drawable icon = task.icon.getConstantState().newDrawable().mutate();
+            btv.applyIconAndLabel(icon, task.titleDescription);
+        }
+        btv.setTag(task);
+    }
+
+    protected void updateRecentTaskAtIndex(int taskIndex, Task task) {
+        View taskView = getChildAt(mRecentsStartIndex + taskIndex);
+        if (taskView instanceof BubbleTextView) {
+            applyTaskToBubbleTextView((BubbleTextView) taskView, task);
+        }
+    }
+
+    /**
+     * Make the divider VISIBLE between the Hotseat and Recents if there is at least one icon in
+     * each, otherwise make it GONE.
+     */
+    private void updateHotseatRecentsDividerVisibility() {
+        if (mHotseatRecentsDivider == null) {
+            return;
+        }
+
+        boolean hasAtLeastOneHotseatItem = false;
+        for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
+            if (getChildAt(i).getVisibility() != GONE) {
+                hasAtLeastOneHotseatItem = true;
+                break;
+            }
+        }
+
+        boolean hasAtLeastOneRecentItem = false;
+        for (int i = mRecentsStartIndex; i <= mRecentsEndIndex; i++) {
+            if (getChildAt(i).getVisibility() != GONE) {
+                hasAtLeastOneRecentItem = true;
+                break;
+            }
+        }
+
+        mHotseatRecentsDivider.setVisibility(hasAtLeastOneHotseatItem && hasAtLeastOneRecentItem
+                ? VISIBLE : GONE);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a00ce56..ee00ed2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -24,6 +24,8 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
+import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
@@ -81,6 +83,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.stream.Stream;
 
 public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -105,6 +108,15 @@
 
     @Override
     protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
+        // If the app launch is from DeviceSearchResultContainer then add the InstanceId from
+        // LiveSearchManager to recreate the AllApps search session on the server side.
+        Optional<InstanceId> logInstanceId = this.getLiveSearchManager().getLogInstanceId();
+        if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS
+                && info.getContainerInfo().getExtendedContainers().getContainerCase()
+                == DEVICE_SEARCH_RESULT_CONTAINER && logInstanceId.isPresent()) {
+            instanceId = logInstanceId.get();
+        }
+
         StatsLogger logger = getStatsLogManager()
                 .logger().withItemInfo(info).withInstanceId(instanceId);
 
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index d949126..d22496d 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.logging;
 
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
@@ -72,6 +73,7 @@
     private static final int DEFAULT_PAGE_INDEX = -2;
     private static final int FOLDER_HIERARCHY_OFFSET = 100;
     private static final int SEARCH_RESULT_HIERARCHY_OFFSET = 200;
+    private static final int EXTENDED_CONTAINERS_HIERARCHY_OFFSET = 300;
 
     public static final CopyOnWriteArrayList<StatsLogConsumer> LOGS_CONSUMER =
             new CopyOnWriteArrayList<>();
@@ -397,6 +399,9 @@
         } else if (info.getContainerInfo().getContainerCase() == SEARCH_RESULT_CONTAINER) {
             return info.getContainerInfo().getSearchResultContainer().getParentContainerCase()
                     .getNumber() + SEARCH_RESULT_HIERARCHY_OFFSET;
+        } else if (info.getContainerInfo().getContainerCase() == EXTENDED_CONTAINERS) {
+            return info.getContainerInfo().getExtendedContainers().getContainerCase().getNumber()
+                    + EXTENDED_CONTAINERS_HIERARCHY_OFFSET;
         } else {
             return info.getContainerInfo().getContainerCase().getNumber();
         }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2f2b566..d9d0a93 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2277,7 +2277,9 @@
     }
 
     public void redrawLiveTile() {
-        mLiveTileTaskViewSimulator.apply(mLiveTileParams);
+        if (mLiveTileParams.getTargetSet() != null) {
+            mLiveTileTaskViewSimulator.apply(mLiveTileParams);
+        }
     }
 
     public TaskViewSimulator getLiveTileTaskViewSimulator() {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5007ffc..6f446ad 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -359,6 +359,16 @@
     }
 
     /**
+     * Directly set the icon and label.
+     */
+    @UiThread
+    public void applyIconAndLabel(Drawable icon, CharSequence label) {
+        setIcon(icon);
+        setText(label);
+        setContentDescription(label);
+    }
+
+    /**
      * Overrides the default long press timeout.
      */
     public void setLongPressTimeoutFactor(float longPressTimeoutFactor) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f681d75..4d5bd5d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -168,8 +168,10 @@
         // Constants from resources
         float swDPs = Utilities.dpiFromPx(
                 Math.min(info.smallestSize.x, info.smallestSize.y), info.metrics);
-        isTablet = swDPs >= TABLET_MIN_DPS;
-        isLargeTablet = swDPs >= LARGE_TABLET_MIN_DPS;
+        boolean allowRotation = context.getResources().getBoolean(R.bool.allow_rotation);
+        // Tablet UI is built with assumption that simulated landscape is disabled.
+        isTablet = allowRotation && swDPs >= TABLET_MIN_DPS;
+        isLargeTablet = isTablet && swDPs >= LARGE_TABLET_MIN_DPS;
         isPhone = !isTablet && !isLargeTablet;
         aspectRatio = ((float) Math.max(widthPx, heightPx)) / Math.min(widthPx, heightPx);
         boolean isTallDevice = Float.compare(aspectRatio, TALL_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0;
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
index ec33908..c2f0b96 100644
--- a/src/com/android/launcher3/allapps/search/LiveSearchManager.java
+++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
@@ -33,10 +33,13 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 
 import java.util.HashMap;
+import java.util.Optional;
 
 /**
  * Manages Lifecycle for Live search results
@@ -51,6 +54,7 @@
             new HashMap<>();
     private final HashMap<Uri, LiveData<Slice>> mUriSliceMap = new HashMap<>();
     private SearchWidgetHost mSearchWidgetHost;
+    private InstanceId mLogInstanceId;
 
     public LiveSearchManager(Launcher launcher) {
         mLauncher = launcher;
@@ -113,6 +117,7 @@
      */
     public void start() {
         stop();
+        mLogInstanceId = new InstanceIdSequence().newInstanceId();
         mSearchWidgetHost = new SearchWidgetHost(mLauncher);
         mSearchWidgetHost.startListening();
     }
@@ -136,6 +141,14 @@
         mUriSliceMap.clear();
     }
 
+    /**
+     * Returns {@link InstanceId} that should be used for logging events within search session, if
+     * available.
+     */
+    public Optional<InstanceId> getLogInstanceId() {
+        return Optional.ofNullable(mLogInstanceId);
+    }
+
     static class SearchWidgetHost extends AppWidgetHost {
         SearchWidgetHost(Context context) {
             super(context, SEARCH_APPWIDGET_HOST_ID);
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index b11b419..3851ab0 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -225,7 +225,7 @@
     protected String dumpProperties() {
         return "id=" + id
                 + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
-                + " container=" + LauncherSettings.Favorites.containerToString(container)
+                + " container=" + getContainerInfo()
                 + " targetComponent=" + getTargetComponent()
                 + " screen=" + screenId
                 + " cell(" + cellX + "," + cellY + ")"
@@ -352,7 +352,10 @@
         return itemBuilder;
     }
 
-    protected ContainerInfo getContainerInfo() {
+    /**
+     * Returns {@link ContainerInfo} used when logging this item.
+     */
+    public ContainerInfo getContainerInfo() {
         switch (container) {
             case CONTAINER_HOTSEAT:
                 return ContainerInfo.newBuilder()