Add CUJ Jank interactions for App Pair saving and launching

* Added finishCallback consumer to LauncherAccessibilityDelegate
to inform when adding an item to workspace was completed.
* The logic seemed to be dependent on the parameter
"focusForAccessibility", but all callers of that are currently
passing in true

Bug: 328646540
Test: https://paste.googleplex.com/6232597136408576
Newly added CUJs showing up when playing w/ device

Change-Id: Ia4944f8d23634bb92296938ea2d07a6babf6f77c
Merged-In: Ia4944f8d23634bb92296938ea2d07a6babf6f77c
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index c543307..03133fd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1254,7 +1254,7 @@
                     if (findExactPairMatch) {
                         // We did not find the app pair we were looking for, so launch one.
                         recents.getSplitSelectController().getAppPairsController().launchAppPair(
-                                (AppPairIcon) launchingIconView);
+                                (AppPairIcon) launchingIconView, -1 /*cuj*/);
                     } else {
                         startItemInfoActivity(itemInfos.get(0));
                     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 75dfe30..10a7ff6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
 import static com.android.launcher3.Flags.enablePredictiveBackGesture;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
@@ -1338,7 +1339,8 @@
      * Launches two apps as an app pair.
      */
     public void launchAppPair(AppPairIcon appPairIcon) {
-        mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon);
+        mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon,
+                CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE);
     }
 
     public boolean canStartHomeSafely() {
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 757f1f8..ecb6118 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -19,6 +19,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 
+import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
 import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -40,6 +41,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -62,6 +64,7 @@
 import com.android.quickstep.views.GroupedTaskView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.Arrays;
@@ -112,6 +115,7 @@
      * well on trampoline apps).
      */
     public void saveAppPair(GroupedTaskView gtv) {
+        InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
         TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
         WorkspaceItemInfo recentsInfo1 = attributes[0].getItemInfo();
         WorkspaceItemInfo recentsInfo2 = attributes[1].getItemInfo();
@@ -168,7 +172,13 @@
                 LauncherAccessibilityDelegate delegate =
                         Launcher.getLauncher(mContext).getAccessibilityDelegate();
                 if (delegate != null) {
-                    delegate.addToWorkspace(newAppPair, true);
+                    delegate.addToWorkspace(newAppPair, true, (success) -> {
+                        if (success) {
+                            InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+                        } else {
+                            InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
+                        }
+                    });
                     mStatsLogManager.logger().withItemInfo(newAppPair)
                             .log(StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_SAVE);
                 }
@@ -179,12 +189,18 @@
     /**
      * Launches an app pair by searching the RecentsModel for running instances of each app, and
      * staging either those running instances or launching the apps as new Intents.
+     *
+     * @param cuj Should be an integer from {@link Cuj} or -1 if no CUJ needs to be logged for jank
+     *            monitoring
      */
-    public void launchAppPair(AppPairIcon appPairIcon) {
+    public void launchAppPair(AppPairIcon appPairIcon, int cuj) {
         WorkspaceItemInfo app1 = appPairIcon.getInfo().contents.get(0);
         WorkspaceItemInfo app2 = appPairIcon.getInfo().contents.get(1);
         ComponentKey app1Key = new ComponentKey(app1.getTargetComponent(), app1.user);
         ComponentKey app2Key = new ComponentKey(app2.getTargetComponent(), app2.user);
+        mSplitSelectStateController.setLaunchingCuj(cuj);
+        InteractionJankMonitorWrapper.begin(appPairIcon, cuj);
+
         mSplitSelectStateController.findLastActiveTasksAndRunCallback(
                 Arrays.asList(app1Key, app2Key),
                 false /* findExactPairMatch */,
@@ -343,7 +359,8 @@
                                 && !lastActiveTasksOfAppPair.contains(runningTaskId2)) {
                             // Neither A nor B are on screen, so just launch a new app pair
                             // normally.
-                            launchAppPair(launchingIconView);
+                            launchAppPair(launchingIconView,
+                                    CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
                         } else {
                             // Exactly one app (A or B) is on-screen, so we have to launch the other
                             // on the appropriate side.
@@ -388,7 +405,8 @@
 
                         if (!task1IsOnScreen && !task2IsOnScreen) {
                             // Neither App A nor App B are on-screen, launch the app pair normally.
-                            launchAppPair(launchingIconView);
+                            launchAppPair(launchingIconView,
+                                    CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
                         } else {
                             // Either A or B is on-screen, so launch the other on the appropriate
                             // side.
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 8e2520e..44da8b1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -104,6 +104,7 @@
 import com.android.systemui.animation.RemoteAnimationRunnerCompat;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.splitscreen.ISplitSelectListener;
 
@@ -151,6 +152,12 @@
     /** True when the first selected split app is being launched in fullscreen. */
     private boolean mLaunchingFirstAppFullscreen;
 
+    /**
+     * Should be a constant from {@link com.android.internal.jank.Cuj} or -1, does not need to be
+     * set for all launches.
+     */
+    private int mLaunchCuj = -1;
+
     private FloatingTaskView mFirstFloatingTaskView;
     private SplitInstructionsView mSplitInstructionsView;
 
@@ -707,6 +714,10 @@
         return mSplitAnimationController;
     }
 
+    public void setLaunchingCuj(int launchCuj) {
+        mLaunchCuj = launchCuj;
+    }
+
     /**
      * Requires Shell Transitions
      */
@@ -850,6 +861,11 @@
         mSplitInstructionsView = null;
         mLaunchingFirstAppFullscreen = false;
 
+        if (mLaunchCuj != -1) {
+            InteractionJankMonitorWrapper.end(mLaunchCuj);
+        }
+        mLaunchCuj = -1;
+
         if (mSessionInstanceIds != null) {
             mStatsLogManager.logger()
                     .withInstanceId(mSessionInstanceIds.second)
diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index 510faf6..adaf7ff 100644
--- a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -105,7 +105,7 @@
         whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo)
         whenever(mockTask1.getKey()).thenReturn(mockTaskKey1)
         whenever(mockTask2.getKey()).thenReturn(mockTaskKey2)
-        doNothing().whenever(spyAppPairsController).launchAppPair(any())
+        doNothing().whenever(spyAppPairsController).launchAppPair(any(), any())
         doNothing()
             .whenever(spyAppPairsController)
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
@@ -210,7 +210,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchAppPair and launchToSide were never called
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, never())
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
     }
@@ -234,7 +234,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
     }
@@ -258,7 +258,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
     }
@@ -282,7 +282,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
     }
@@ -306,7 +306,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
     }
@@ -330,7 +330,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchAppPair was called
-        verify(spyAppPairsController, times(1)).launchAppPair(any())
+        verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
         verify(spyAppPairsController, never())
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
     }
@@ -354,7 +354,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_BOTTOM_OR_RIGHT))
     }
@@ -378,7 +378,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchToSide was called with the correct arguments
-        verify(spyAppPairsController, never()).launchAppPair(any())
+        verify(spyAppPairsController, never()).launchAppPair(any(), any())
         verify(spyAppPairsController, times(1))
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), eq(STAGE_POSITION_TOP_OR_LEFT))
     }
@@ -402,7 +402,7 @@
         callback.accept(arrayOf(mockTask1, mockTask2))
 
         // Verify that launchAppPair was called
-        verify(spyAppPairsController, times(1)).launchAppPair(any())
+        verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
         verify(spyAppPairsController, never())
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
     }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index e861d38..30fa7ff 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -22,6 +22,8 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.CellLayout;
@@ -58,6 +60,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class LauncherAccessibilityDelegate extends BaseAccessibilityDelegate<Launcher> {
 
@@ -173,7 +176,7 @@
         } else if (action == MOVE) {
             return beginAccessibleDrag(host, item, fromKeyboard);
         } else if (action == ADD_TO_WORKSPACE) {
-            return addToWorkspace(item, true);
+            return addToWorkspace(item, true /*accessibility*/, null /*finishCallback*/);
         } else if (action == MOVE_TO_WORKSPACE) {
             return moveToWorkspace(item);
         } else if (action == RESIZE) {
@@ -384,13 +387,19 @@
      * @param item item to be added
      * @param accessibility true if the first item to be added to the workspace
      *     should be focused for accessibility.
+     * @param finishCallback Callback which will be run after this item has been added
+     *                       and the view has been transitioned to the workspace, or on failure.
      *
      * @return true if the item could be successfully added
      */
-    public boolean addToWorkspace(ItemInfo item, boolean accessibility) {
+    public boolean addToWorkspace(ItemInfo item, boolean accessibility,
+            @Nullable Consumer<Boolean> finishCallback) {
         final int[] coordinates = new int[2];
         final int screenId = findSpaceOnWorkspace(item, coordinates);
         if (screenId == -1) {
+            if (finishCallback != null) {
+                finishCallback.accept(false /*success*/);
+            }
             return false;
         }
         mContext.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
@@ -400,7 +409,7 @@
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
 
-                bindItem(item, accessibility);
+                bindItem(item, accessibility, finishCallback);
                 announceConfirmation(R.string.item_added_to_workspace);
             } else if (item instanceof PendingAddItemInfo) {
                 PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -413,7 +422,7 @@
                 mContext.getModelWriter().addItemToDatabase(info,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
-                bindItem(info, accessibility);
+                bindItem(info, accessibility, finishCallback);
             } else if (item instanceof FolderInfo fi) {
                 Workspace<?> workspace = mContext.getWorkspace();
                 workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
@@ -423,23 +432,30 @@
                 fi.contents.forEach(member -> {
                     mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1);
                 });
-                bindItem(fi, accessibility);
+                bindItem(fi, accessibility, finishCallback);
             }
         }));
         return true;
     }
 
-    private void bindItem(ItemInfo item, boolean focusForAccessibility) {
+    private void bindItem(ItemInfo item, boolean focusForAccessibility,
+            @Nullable Consumer<Boolean> finishCallback) {
         View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter());
         if (view == null) {
+            if (finishCallback != null) {
+                finishCallback.accept(false /*success*/);
+            }
             return;
         }
-        AnimatorSet anim = null;
-        if (focusForAccessibility) {
-            anim = new AnimatorSet();
-            anim.addListener(forEndCallback(
-                    () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
-        }
+        AnimatorSet anim = new AnimatorSet();
+        anim.addListener(forEndCallback((success) -> {
+            if (focusForAccessibility) {
+                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+            }
+            if (finishCallback != null) {
+                finishCallback.accept(success);
+            }
+        }));
         mContext.bindInflatedItems(Collections.singletonList(Pair.create(item, view)), anim);
     }