Support splitting from workspace with Widgets

* Need to insert widget's icon in animation
* Launching w/ same package app + widget is
broken

Test: Launched apps from predicted apps + widget,
hotseat apps + widget
Flag: ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE
Bug: 276361926

Change-Id: I3e30189e56536371ebd0acfbdd2c073a882cc731
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 08d147f..163c36f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -56,6 +56,10 @@
             return RemoteViews.startPendingIntent(hostView, pendingIntent,
                     remoteResponse.getLaunchOptions(view));
         }
+        if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
+                pendingIntent)) {
+            return true;
+        }
         Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
         ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
                 .getActivityLaunchOptions(hostView);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b2b0623..5b90527 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -970,8 +970,8 @@
         return mTaskbarUIController;
     }
 
-    public SplitSelectStateController getSplitSelectStateController() {
-        return mSplitSelectStateController;
+    public SplitToWorkspaceController getSplitToWorkspaceController() {
+        return mSplitToWorkspaceController;
     }
 
     public <T extends OverviewActionsView> T getActionsView() {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index d44d7f6..9fc6ad6 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -97,8 +97,15 @@
     private UserHandle mInitialUser;
     private int mInitialTaskId = INVALID_TASK_ID;
     /** {@link #mSecondTaskIntent} and {@link #mSecondUser} (the user of the Intent) are set
-     * together when split is confirmed with an Intent. */
+     * together when split is confirmed with an Intent. Either this or {@link #mSecondPendingIntent}
+     * will be set, but not both
+     */
     private Intent mSecondTaskIntent;
+    /**
+     * Set when split is confirmed via a widget. Either this or {@link #mSecondTaskIntent} will be
+     * set, but not both
+     */
+    private PendingIntent mSecondPendingIntent;
     private UserHandle mSecondUser;
     private int mSecondTaskId = INVALID_TASK_ID;
     private boolean mRecentsAnimationRunning;
@@ -247,6 +254,16 @@
     }
 
     /**
+     * To be called as soon as user selects the second app (even if animations aren't complete)
+     * Sets {@link #mSecondUser} from that of the pendingIntent
+     * @param pendingIntent The second PendingIntent that will be launched.
+     */
+    public void setSecondTask(PendingIntent pendingIntent) {
+        mSecondPendingIntent = pendingIntent;
+        mSecondUser = pendingIntent.getCreatorUserHandle();
+    }
+
+    /**
      * To be called when we want to launch split pairs from an existing GroupedTaskView.
      */
     public void launchTasks(GroupedTaskView groupedTaskView, Consumer<Boolean> callback,
@@ -290,17 +307,18 @@
         if (freezeTaskList) {
             options1.setFreezeRecentTasksReordering();
         }
+        boolean hasSecondaryPendingIntent = mSecondPendingIntent != null;
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
             final RemoteSplitLaunchTransitionRunner animationRunner =
                     new RemoteSplitLaunchTransitionRunner(taskId1, taskId2, callback);
             final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
                     ActivityThread.currentActivityThread().getApplicationThread(),
                     "LaunchSplitPair");
-            if (intent1 == null && intent2 == null) {
+            if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
                 mSystemUiProxy.startTasks(taskId1, options1.toBundle(), taskId2,
                         null /* options2 */, stagePosition, splitRatio, remoteTransition,
                         shellInstanceId);
-            } else if (intent2 == null) {
+            } else if (intent2 == null && !hasSecondaryPendingIntent) {
                 launchIntentOrShortcut(intent1, mInitialUser, options1, taskId2, stagePosition,
                         splitRatio, remoteTransition, shellInstanceId);
             } else if (intent1 == null) {
@@ -310,7 +328,9 @@
             } else {
                 mSystemUiProxy.startIntents(getPendingIntent(intent1, mInitialUser),
                         getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
-                        getPendingIntent(intent2, mSecondUser),
+                        hasSecondaryPendingIntent
+                                ? mSecondPendingIntent
+                                : getPendingIntent(intent2, mSecondUser),
                         getShortcutInfo(intent2, mSecondUser), null /* options2 */,
                         stagePosition, splitRatio, remoteTransition, shellInstanceId);
             }
@@ -321,11 +341,11 @@
                     animationRunner, 300, 150,
                     ActivityThread.currentActivityThread().getApplicationThread());
 
-            if (intent1 == null && intent2 == null) {
+            if (intent1 == null && (intent2 == null && !hasSecondaryPendingIntent)) {
                 mSystemUiProxy.startTasksWithLegacyTransition(taskId1, options1.toBundle(),
                         taskId2, null /* options2 */, stagePosition, splitRatio, adapter,
                         shellInstanceId);
-            } else if (intent2 == null) {
+            } else if (intent2 == null && !hasSecondaryPendingIntent) {
                 launchIntentOrShortcutLegacy(intent1, mInitialUser, options1, taskId2,
                         stagePosition, splitRatio, adapter, shellInstanceId);
             } else if (intent1 == null) {
@@ -336,7 +356,9 @@
                 mSystemUiProxy.startIntentsWithLegacyTransition(
                         getPendingIntent(intent1, mInitialUser),
                         getShortcutInfo(intent1, mInitialUser), options1.toBundle(),
-                        getPendingIntent(intent2, mSecondUser),
+                        hasSecondaryPendingIntent
+                                ? mSecondPendingIntent
+                                : getPendingIntent(intent2, mSecondUser),
                         getShortcutInfo(intent2, mSecondUser), null /* options2 */, stagePosition,
                         splitRatio, adapter, shellInstanceId);
             }
@@ -374,7 +396,22 @@
         }
     }
 
+    /**
+     * We treat launching by intents as grouped in two ways,
+     * If {@param intent} represents the first app, we always convert the intent to pending intent
+     * It it represents second app, either the second intent OR mSecondPendingIntent will be used
+     *    convert second intent to a pendingIntent OR return mSecondPendingIntent as is
+     */
     private PendingIntent getPendingIntent(Intent intent, UserHandle user) {
+        boolean isParamFirstIntent = intent != null && intent == mInitialTaskIntent;
+        if (!isParamFirstIntent && mSecondPendingIntent != null) {
+            // Because mSecondPendingIntent and mSecondTaskIntent can't both be set, we know we need
+            // to be using mSecondPendingIntent
+            return mSecondPendingIntent;
+        }
+
+        // intent param must either be mInitialTaskIntent or mSecondTaskIntent, convert either to
+        // a new PendingIntent
         return intent == null ? null : (user != null
                 ? PendingIntent.getActivityAsUser(mContext, 0, intent,
                 FLAG_MUTABLE | FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT, null /* options */, user)
@@ -546,6 +583,7 @@
         mSplitEvent = null;
         mAnimateCurrentTaskDismissal = false;
         mDismissingFromSplitPair = false;
+        mSecondPendingIntent = null;
     }
 
     /**
@@ -577,7 +615,8 @@
     }
 
     private boolean isSecondTaskIntentSet() {
-        return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
+        return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null
+                || mSecondPendingIntent != null);
     }
 
     public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index dd10c2d..148a45a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -21,9 +21,15 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.view.View;
 
@@ -56,13 +62,37 @@
     }
 
     /**
+     * Handles widget selection from staged split.
+     * @param view Original widget view
+     * @param pendingIntent Provided by widget via InteractionHandler
+     * @return {@code true} if we can attempt launch the widget into split, {@code false} otherwise
+     *         to allow launcher to handle the click
+     */
+    public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent) {
+        if (shouldIgnoreSecondSplitLaunch()) {
+            return false;
+        }
+
+        // Convert original widgetView into bitmap to use for animation
+        // TODO(b/276361926) get the icon for this widget via PackageManager?
+        int width = view.getWidth();
+        int height = view.getHeight();
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        view.draw(canvas);
+
+        mController.setSecondTask(pendingIntent);
+
+        startWorkspaceAnimation(view, bitmap, null /*icon*/);
+        return true;
+    }
+
+    /**
      * Handles second app selection from stage split. If the item can't be opened in split or
      * it's not in stage split state, we pass it onto Launcher's default item click handler.
      */
     public boolean handleSecondAppSelectionForSplit(View view) {
-        if ((!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
-                && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get())
-                || !mController.isSplitSelectActive()) {
+        if (shouldIgnoreSecondSplitLaunch()) {
             return false;
         }
         Object tag = view.getTag();
@@ -86,6 +116,12 @@
 
         mController.setSecondTask(intent, user);
 
+        startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher));
+        return true;
+    }
+
+    private void startWorkspaceAnimation(@NonNull View view, @Nullable Bitmap bitmap,
+            @Nullable Drawable icon) {
         boolean isTablet = mLauncher.getDeviceProfile().isTablet;
         SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet);
         PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration());
@@ -107,8 +143,7 @@
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
 
         FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher,
-                view, null /* thumbnail */, bitmapInfo.newIcon(mLauncher),
-                secondTaskStartingBounds);
+                view, bitmap, icon, secondTaskStartingBounds);
         secondFloatingTaskView.setAlpha(1);
         secondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds,
                 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */);
@@ -138,6 +173,11 @@
             }
         });
         pendingAnimation.buildAnim().start();
-        return true;
+    }
+
+    private boolean shouldIgnoreSecondSplitLaunch() {
+        return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
+                && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get())
+                || !mController.isSplitSelectActive();
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 512df8e..acfd54c 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -18,6 +18,7 @@
 package com.android.quickstep.util
 
 import android.app.ActivityManager
+import android.app.PendingIntent
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -32,6 +33,8 @@
 import com.android.launcher3.statemanager.StateManager
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.launcher3.util.mock
 import com.android.launcher3.util.withArgCaptor
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.SystemUiProxy
@@ -59,6 +62,7 @@
     @Mock lateinit var handler: Handler
     @Mock lateinit var context: Context
     @Mock lateinit var recentsModel: RecentsModel
+    @Mock lateinit var pendingIntent: PendingIntent
 
     lateinit var splitSelectStateController: SplitSelectStateController
 
@@ -348,6 +352,14 @@
         assertFalse(splitSelectStateController.isSplitSelectActive)
     }
 
+    @Test
+    fun secondPendingIntentSet() {
+        val itemInfo = ItemInfo()
+        splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
+        splitSelectStateController.setSecondTask(pendingIntent)
+        assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
+    }
+
     // Generate GroupTask with default userId.
     private fun generateGroupTask(
         task1ComponentName: ComponentName,