Introduce non-null `itemInfo` inside TaskView

Let `TaskView` have a non-null `itemInfo` even it has no
tasks (no corresponding TaskContainer). Thus, we can still
go forward to log the necessary info of the `TaskView`
without any tasks.

Flag: EXEMPT refactor
Fix: 391918297
Test: TaskViewItemInfoTest
Change-Id: Idd08eb9846b1cd2043dd0087bc3e0078bb0b8247
diff --git a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
index ee28d7a..c201ab1 100644
--- a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
+++ b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.Intent
+import android.os.Process
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.VisibleForTesting.Companion.PRIVATE
 import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
@@ -26,34 +27,40 @@
 import com.android.launcher3.pm.UserCache
 import com.android.quickstep.TaskUtils
 import com.android.quickstep.views.TaskContainer
+import com.android.quickstep.views.TaskView
 
-class TaskViewItemInfo(taskContainer: TaskContainer) : WorkspaceItemInfo() {
+class TaskViewItemInfo(taskView: TaskView, taskContainer: TaskContainer?) : WorkspaceItemInfo() {
     @VisibleForTesting(otherwise = PRIVATE) val taskViewAtom: LauncherAtom.TaskView
 
     init {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
         container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
-        val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
-        user = componentKey.user
-        intent = Intent().setComponent(componentKey.componentName)
-        title = taskContainer.task.title
-        if (privateSpaceRestrictAccessibilityDrag()) {
-            if (
-                UserCache.getInstance(taskContainer.taskView.context)
-                    .getUserInfo(componentKey.user)
-                    .isPrivate
-            ) {
-                runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+        val componentName: String
+        if (taskContainer != null) {
+            val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
+            user = componentKey.user
+            intent = Intent().setComponent(componentKey.componentName)
+            title = taskContainer.task.title
+            if (privateSpaceRestrictAccessibilityDrag()) {
+                if (
+                    UserCache.getInstance(taskView.context).getUserInfo(componentKey.user).isPrivate
+                ) {
+                    runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+                }
             }
+            componentName = componentKey.componentName.flattenToShortString()
+        } else {
+            user = Process.myUserHandle()
+            intent = Intent()
+            componentName = ""
         }
 
         taskViewAtom =
             createTaskViewAtom(
-                type = taskContainer.taskView.type.ordinal,
-                index =
-                    taskContainer.taskView.recentsView?.indexOfChild(taskContainer.taskView) ?: -1,
-                componentName = componentKey.componentName.flattenToShortString(),
-                cardinality = taskContainer.taskView.taskContainers.size,
+                type = taskView.type.ordinal,
+                index = taskView.recentsView?.indexOfChild(taskView) ?: -1,
+                componentName,
+                cardinality = taskView.taskContainers.size,
             )
     }
 
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 5f8b89a..f4400fa 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -120,7 +120,6 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
@@ -1491,7 +1490,7 @@
                 startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
-    private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
+    private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTaskView) {
         if (mDp == null || !mDp.isGestureMode) {
             // We probably never received an animation controller, skip logging.
             return;
@@ -1509,9 +1508,9 @@
             case NEW_TASK:
                 events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
                         : LAUNCHER_QUICKSWITCH_RIGHT);
-                if (targetTask != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+                if (targetTaskView != null && DesktopModeStatus.canEnterDesktopMode(mContext)
                         && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
-                    if (targetTask.getType() == TaskViewType.DESKTOP) {
+                    if (targetTaskView.getType() == TaskViewType.DESKTOP) {
                         events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
                     } else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
                         events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
@@ -1528,9 +1527,8 @@
                 .withInputType(mGestureState.isTrackpadGesture()
                         ? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
                         : SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
-        ItemInfo itemInfo;
-        if (targetTask != null && (itemInfo = targetTask.getFirstItemInfo()) != null) {
-            logger.withItemInfo(itemInfo);
+        if (targetTaskView != null) {
+            logger.withItemInfo(targetTaskView.getItemInfo());
         }
 
         int pageIndex = endTarget == LAST_TASK || mRecentsView == null
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ee1d8a6..deeaacc 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -135,7 +135,7 @@
         public SplitSelectSystemShortcut(RecentsViewContainer container,
                 TaskContainer taskContainer, TaskView taskView,
                 SplitPositionOption option) {
-            super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
+            super(option.iconResId, option.textResId, container, taskContainer.getItemInfo(),
                     taskView);
             mTaskContainer = taskContainer;
             mSplitPositionOption = option;
@@ -163,8 +163,7 @@
 
         public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
             int iconResId) {
-            super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
-                    taskView);
+            super(iconResId, R.string.save_app_pair, container, taskView.getItemInfo(), taskView);
             mTaskView = taskView;
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 1b59f5b..79685ac 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4085,9 +4085,8 @@
                         } else {
                             removeTaskInternal(dismissedTaskView);
                         }
-                        // TODO(b/391918297): Logging when the TaskView does not have tasks as well.
                         mContainer.getStatsLogManager().logger()
-                                .withItemInfo(dismissedTaskView.getFirstItemInfo())
+                                .withItemInfo(dismissedTaskView.getItemInfo())
                                 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
                     }
 
@@ -5772,12 +5771,8 @@
                 } else {
                     taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
                 }
-                // TODO(b/391918297): Logging when there is no associated task.
-                ItemInfo firstItemInfo = taskView.getFirstItemInfo();
-                if (firstItemInfo != null) {
-                    mContainer.getStatsLogManager().logger().withItemInfo(firstItemInfo)
-                            .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
-                }
+                mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
+                        .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
             } else {
                 onTaskLaunchAnimationEnd(false);
             }
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index a9e84ef..2c6ecb1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -106,7 +106,7 @@
 
     /** Builds proto for logging */
     val itemInfo: TaskViewItemInfo
-        get() = TaskViewItemInfo(this)
+        get() = TaskViewItemInfo(taskView, this)
 
     fun bind() {
         digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 9807b0d..15982ad 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -55,6 +55,7 @@
 import com.android.launcher3.anim.AnimatedFloat
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskViewItemInfo
 import com.android.launcher3.testing.TestLogging
 import com.android.launcher3.testing.shared.TestProtocol
 import com.android.launcher3.util.CancellableTask
@@ -170,6 +171,15 @@
     val firstItemInfo: ItemInfo?
         get() = firstTaskContainer?.itemInfo
 
+    /**
+     * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some
+     * specific information like user, title etc of the Task. However, these task specific
+     * information will be skipped if the TaskView has no [taskContainers]. Note, please use
+     * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer].
+     */
+    val itemInfo: TaskViewItemInfo
+        get() = TaskViewItemInfo(this, firstTaskContainer)
+
     protected val container: RecentsViewContainer =
         RecentsViewContainer.containerFromContext(context)
     protected val lastTouchDownPosition = PointF()
@@ -1104,13 +1114,10 @@
                 }
             }
         Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
-        // TODO(b/391918297): Logging when there is no associated task.
-        firstItemInfo?.let {
-            container.statsLogManager
-                .logger()
-                .withItemInfo(it)
-                .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
-        }
+        container.statsLogManager
+            .logger()
+            .withItemInfo(itemInfo)
+            .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
     }
 
     /** Launch of the current task (both live and inactive tasks) with an animation. */
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index be71640..de0da64 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.ComponentName
 import android.content.Intent
+import android.os.Process
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
@@ -33,7 +34,6 @@
 import com.android.launcher3.util.UserIconInfo
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.TestComponent
 import com.android.quickstep.recents.di.RecentsDependencies
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.views.RecentsView
@@ -84,7 +84,7 @@
         whenever(taskView.type).thenReturn(TaskViewType.SINGLE)
         whenever(taskView.taskContainers).thenReturn(taskContainers)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -105,7 +105,7 @@
         whenever(taskView.type).thenReturn(TaskViewType.GROUPED)
         whenever(taskView.taskContainers).thenReturn(taskContainers)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -130,7 +130,7 @@
         whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
         whenever(taskView.taskContainers).thenReturn(taskContainers)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -151,7 +151,7 @@
         whenever(taskView.taskContainers).thenReturn(taskContainers)
         whenever(userInfo.isPrivate).thenReturn(true)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -166,6 +166,25 @@
             .isEqualTo(FLAG_NOT_PINNABLE)
     }
 
+    @Test
+    fun emptyDesktopTask() {
+        whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
+
+        val taskViewItemInfo = TaskViewItemInfo(taskView = taskView, taskContainer = null)
+
+        assertThat(taskViewItemInfo.taskViewAtom)
+            .isEqualTo(
+                createTaskViewAtom(
+                    type = 2,
+                    index = TASK_VIEW_INDEX,
+                    componentName = "",
+                    cardinality = 0,
+                )
+            )
+        assertThat(taskViewItemInfo.user).isEqualTo(Process.myUserHandle())
+        assertThat(taskViewItemInfo.intent).isNotNull()
+    }
+
     private fun createTask(id: Int) =
         Task(TaskKey(id, 0, Intent(), ComponentName(PACKAGE, CLASS), 0, 2000))