Merge "Support splitting from workspace with Widgets" into udc-dev

This commit is contained in:
Vinit Nayak
2023-04-22 03:55:23 +00:00
committed by Android (Google) Code Review
5 changed files with 111 additions and 16 deletions
@@ -56,6 +56,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
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);
@@ -973,8 +973,8 @@ public class QuickstepLauncher extends Launcher {
return mTaskbarUIController;
}
public SplitSelectStateController getSplitSelectStateController() {
return mSplitSelectStateController;
public SplitToWorkspaceController getSplitToWorkspaceController() {
return mSplitToWorkspaceController;
}
public <T extends OverviewActionsView> T getActionsView() {
@@ -98,8 +98,15 @@ public class SplitSelectStateController {
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;
@@ -248,6 +255,16 @@ public class SplitSelectStateController {
mSecondUser = user;
}
/**
* 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.
*/
@@ -292,17 +309,18 @@ public class SplitSelectStateController {
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 @@ public class SplitSelectStateController {
} 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 @@ public class SplitSelectStateController {
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 @@ public class SplitSelectStateController {
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 @@ public class SplitSelectStateController {
}
}
/**
* 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 @@ public class SplitSelectStateController {
mSplitEvent = null;
mAnimateCurrentTaskDismissal = false;
mDismissingFromSplitPair = false;
mSecondPendingIntent = null;
}
/**
@@ -579,7 +617,8 @@ public class SplitSelectStateController {
}
private boolean isSecondTaskIntentSet() {
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null);
return (mSecondTaskId != INVALID_TASK_ID || mSecondTaskIntent != null
|| mSecondPendingIntent != null);
}
public void setFirstFloatingTaskView(FloatingTaskView floatingTaskView) {
@@ -21,9 +21,15 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSP
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;
@@ -55,14 +61,38 @@ public class SplitToWorkspaceController {
R.dimen.multi_window_task_divider_size) / 2;
}
/**
* 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 @@ public class SplitToWorkspaceController {
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 @@ public class SplitToWorkspaceController {
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 @@ public class SplitToWorkspaceController {
}
});
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();
}
}
@@ -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.statehandlers.DepthController
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 @@ class SplitSelectStateControllerTest {
@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 @@ class SplitSelectStateControllerTest {
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,