Merge "Support splitting from workspace with Widgets" into udc-dev
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 34d27c2..d67dbae 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -973,8 +973,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 c035a78..60af09f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -98,8 +98,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;
@@ -249,6 +256,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,
@@ -292,17 +309,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) {
@@ -312,7 +330,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);
}
@@ -323,11 +343,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) {
@@ -338,7 +358,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);
}
@@ -376,7 +398,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)
@@ -548,6 +585,7 @@
mSplitEvent = null;
mAnimateCurrentTaskDismissal = false;
mDismissingFromSplitPair = false;
+ mSecondPendingIntent = null;
}
/**
@@ -579,7 +617,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,