Request split launches using a new enum, snapPosition, rather than a float splitRatio

Split ratios are now stored as an int enum rather than float values. Previously, split ratios were stored as floats, which needed to be recalculated frequently and differed depending on device screen size. Now Shell to store and launch split screen setups using an enum rather than floats.

Bug: 274835596
Bug: 182839788
Test: Existing tests still pass
Change-Id: Iebb8d3c7e8f9d597a59b677e48072300ddea458f
This commit is contained in:
Jeremy Sim
2023-09-11 21:59:56 -07:00
parent 02b3cc568c
commit da230cf526
14 changed files with 213 additions and 158 deletions
@@ -50,9 +50,9 @@ import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STA
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1260,10 +1260,8 @@ public class QuickstepLauncher extends Launcher {
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ true,
groupTask.mSplitBounds == null
? DEFAULT_SPLIT_RATIO
: groupTask.mSplitBounds.appsStackedVertically
? groupTask.mSplitBounds.topTaskPercent
: groupTask.mSplitBounds.leftTaskPercent);
? SNAP_TO_50_50
: groupTask.mSplitBounds.snapPosition);
}
/**
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
@@ -39,7 +40,6 @@ import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -293,7 +293,7 @@ public class RecentTasksList {
task2.setLastSnapshotData(taskInfo2);
}
final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertSplitBounds(rawTask.getSplitBounds());
convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
}
@@ -313,15 +313,6 @@ public class RecentTasksList {
return new DesktopTask(tasks);
}
private SplitConfigurationOptions.SplitBounds convertSplitBounds(
SplitBounds shellSplitBounds) {
return shellSplitBounds == null ?
null :
new SplitConfigurationOptions.SplitBounds(
shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
}
private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
ArrayList<GroupTask> newTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
@@ -138,7 +138,8 @@ public class RecentsAnimationCallbacks implements
nonAppTargets = new RemoteAnimationTarget[0];
}
final RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
wallpaperTargets, nonAppTargets, homeContentInsets, minimizedHomeBounds);
wallpaperTargets, nonAppTargets, homeContentInsets, minimizedHomeBounds,
extras);
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureLog.INSTANCE.addLog(
@@ -20,6 +20,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import android.app.WindowConfiguration;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
import com.android.quickstep.views.DesktopTaskView;
@@ -35,8 +36,8 @@ public class RecentsAnimationTargets extends RemoteAnimationTargets {
public RecentsAnimationTargets(RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
Rect homeContentInsets, Rect minimizedHomeBounds) {
super(apps, wallpapers, nonApps, MODE_CLOSING);
Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) {
super(apps, wallpapers, nonApps, MODE_CLOSING, extras);
this.homeContentInsets = homeContentInsets;
this.minimizedHomeBounds = minimizedHomeBounds;
}
@@ -19,6 +19,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import android.os.Bundle;
import android.view.RemoteAnimationTarget;
import java.util.ArrayList;
@@ -35,6 +36,7 @@ public class RemoteAnimationTargets {
public final RemoteAnimationTarget[] apps;
public final RemoteAnimationTarget[] wallpapers;
public final RemoteAnimationTarget[] nonApps;
public final Bundle extras;
public final int targetMode;
public final boolean hasRecents;
@@ -42,7 +44,7 @@ public class RemoteAnimationTargets {
public RemoteAnimationTargets(RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
int targetMode) {
int targetMode, Bundle extras) {
ArrayList<RemoteAnimationTarget> filteredApps = new ArrayList<>();
boolean hasRecents = false;
if (apps != null) {
@@ -61,6 +63,13 @@ public class RemoteAnimationTargets {
this.targetMode = targetMode;
this.hasRecents = hasRecents;
this.nonApps = nonApps;
this.extras = extras;
}
public RemoteAnimationTargets(RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
int targetMode) {
this(apps, wallpapers, nonApps, targetMode, new Bundle());
}
public RemoteAnimationTarget findTask(int taskId) {
@@ -16,7 +16,9 @@
package com.android.quickstep;
import android.app.WindowConfiguration;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
@@ -24,11 +26,12 @@ import android.view.RemoteAnimationTarget;
import androidx.annotation.Nullable;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.DesktopTaskView;
import com.android.wm.shell.util.SplitBounds;
import java.util.ArrayList;
import java.util.Arrays;
@@ -43,7 +46,7 @@ public class RemoteTargetGluer {
private static final int DEFAULT_NUM_HANDLES = 2;
private RemoteTargetHandle[] mRemoteTargetHandles;
private SplitBounds mSplitBounds;
private SplitConfigurationOptions.SplitBounds mSplitBounds;
/**
* Use this constructor if remote targets are split-screen independent
@@ -110,6 +113,16 @@ public class RemoteTargetGluer {
return mRemoteTargetHandles;
}
/**
* Calls {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)} with SplitBounds
* information specified.
*/
public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
SplitConfigurationOptions.SplitBounds splitBounds) {
mSplitBounds = splitBounds;
return assignTargetsForSplitScreen(targets);
}
/**
* Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this assigns the
* apps in {@code targets.apps} to the {@link #mRemoteTargetHandles} with index 0 will being
@@ -129,10 +142,17 @@ public class RemoteTargetGluer {
mRemoteTargetHandles = newHandles;
}
boolean containsSplitTargets = Arrays.stream(targets.apps)
.anyMatch(remoteAnimationTarget ->
remoteAnimationTarget.windowConfiguration.getWindowingMode()
== WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
// If we are in a true split screen case (2 apps running on screen), either:
// a) mSplitBounds was already set (from the clicked GroupedTaskView)
// b) A SplitBounds was passed up from shell (via AbsSwipeUpHandler)
// If both of these are null, we are in a 1-app or 1-app-plus-assistant case.
if (mSplitBounds == null) {
SplitBounds shellSplitBounds = targets.extras.getParcelable(KEY_EXTRA_SPLIT_BOUNDS,
SplitBounds.class);
mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
}
boolean containsSplitTargets = mSplitBounds != null;
Log.d(TAG, "containsSplitTargets? " + containsSplitTargets + " handleLength: " +
mRemoteTargetHandles.length + " appsLength: " + targets.apps.length);
@@ -154,40 +174,12 @@ public class RemoteTargetGluer {
}
} else {
// Split apps (+ maybe assistant)
RemoteAnimationTarget topLeftTarget = Arrays.stream(targets.apps)
.filter(remoteAnimationTarget ->
remoteAnimationTarget.windowConfiguration.getWindowingMode()
== WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
.findFirst().get();
// Fetch the adjacent target for split screen.
RemoteAnimationTarget bottomRightTarget = null;
for (int i = 0; i < targets.apps.length; i++) {
final RemoteAnimationTarget target = targets.apps[i];
if (target.windowConfiguration.getWindowingMode() !=
WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW ||
target == topLeftTarget) {
continue;
}
Rect topLeftBounds = getStartBounds(topLeftTarget);
Rect bounds = getStartBounds(target);
if (topLeftBounds.left > bounds.right || topLeftBounds.top > bounds.bottom) {
bottomRightTarget = topLeftTarget;
topLeftTarget = target;
break;
} else if (topLeftBounds.right < bounds.left || topLeftBounds.bottom < bounds.top) {
bottomRightTarget = target;
break;
}
}
RemoteAnimationTarget topLeftTarget = targets.findTask(mSplitBounds.leftTopTaskId);
RemoteAnimationTarget bottomRightTarget = targets.findTask(
mSplitBounds.rightBottomTaskId);
// remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
// vice versa
mSplitBounds = new SplitBounds(
getStartBounds(topLeftTarget),
getStartBounds(bottomRightTarget),
topLeftTarget.taskId,
bottomRightTarget.taskId);
mRemoteTargetHandles[0].mTransformParams.setTargetSet(
createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
@@ -291,7 +283,7 @@ public class RemoteTargetGluer {
return mRemoteTargetHandles;
}
public SplitBounds getSplitBounds() {
public SplitConfigurationOptions.SplitBounds getSplitBounds() {
return mSplitBounds;
}
@@ -19,6 +19,7 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENT_TASKS_MISSING;
import static com.android.quickstep.util.LogUtils.splitFailureMessage;
@@ -63,7 +64,6 @@ import com.android.internal.util.ScreenshotRequest;
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistUtils;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -78,6 +78,7 @@ import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.draganddrop.IDragAndDrop;
@@ -796,12 +797,12 @@ public class SystemUiProxy implements ISystemUiProxy {
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
@StagePosition int splitPosition, @SnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
splitRatio, remoteTransition, instanceId);
snapPosition, remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e);
}
@@ -809,12 +810,13 @@ public class SystemUiProxy implements ISystemUiProxy {
}
public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
int taskId, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
float splitRatio, RemoteTransition remoteTransition, InstanceId instanceId) {
int taskId, Bundle options2, @StagePosition int splitPosition,
@SnapPosition int snapPosition, RemoteTransition remoteTransition,
InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
splitPosition, snapPosition, remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e);
}
@@ -824,13 +826,13 @@ public class SystemUiProxy implements ISystemUiProxy {
public void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
@StagePosition int splitPosition, @SnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntents(pendingIntent1, userId1, shortcutInfo1, options1,
pendingIntent2, userId2, shortcutInfo2, options2, splitPosition, splitRatio,
remoteTransition, instanceId);
pendingIntent2, userId2, shortcutInfo2, options2, splitPosition,
snapPosition, remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e);
}
@@ -838,12 +840,12 @@ public class SystemUiProxy implements ISystemUiProxy {
}
public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
float splitRatio, RemoteTransition remoteTransition, InstanceId instanceId) {
Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
splitPosition, splitRatio, remoteTransition, instanceId);
splitPosition, snapPosition, remoteTransition, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e);
}
@@ -854,12 +856,12 @@ public class SystemUiProxy implements ISystemUiProxy {
* Start multiple tasks in split-screen simultaneously.
*/
public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
splitPosition, splitRatio, adapter, instanceId);
splitPosition, snapPosition, adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage(
"startTasksWithLegacyTransition", "RemoteException"), e);
@@ -868,13 +870,13 @@ public class SystemUiProxy implements ISystemUiProxy {
}
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
Bundle options1, int taskId, Bundle options2,
@SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition,
@SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
options1, taskId, options2, splitPosition, snapPosition, adapter,
instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage(
"startIntentAndTaskWithLegacyTransition", "RemoteException"), e);
@@ -883,12 +885,12 @@ public class SystemUiProxy implements ISystemUiProxy {
}
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
int taskId, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
int taskId, Bundle options2, @StagePosition int splitPosition,
@SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
taskId, options2, splitPosition, splitRatio, adapter, instanceId);
taskId, options2, splitPosition, snapPosition, adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage(
"startShortcutAndTaskWithLegacyTransition", "RemoteException"), e);
@@ -903,13 +905,13 @@ public class SystemUiProxy implements ISystemUiProxy {
public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @SplitConfigurationOptions.StagePosition int sidePosition,
float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
@Nullable Bundle options2, @StagePosition int sidePosition,
@SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2, options2,
sidePosition, splitRatio, adapter, instanceId);
sidePosition, snapPosition, adapter, instanceId);
} catch (RemoteException e) {
Log.w(TAG, splitFailureMessage(
"startIntentsWithLegacyTransition", "RemoteException"), e);
@@ -191,7 +191,8 @@ public final class TaskViewUtils {
if (forDesktop) {
remoteTargetHandles = gluer.assignTargetsForDesktop(targets);
} else if (v.containsMultipleTasks()) {
remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
((GroupedTaskView) v).getSplitBoundsConfig());
} else {
remoteTargetHandles = gluer.assignTargets(targets);
}
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.wm.shell.util.SplitBounds
class SplitScreenUtils {
companion object {
// TODO(b/254378592): Remove these methods when the two classes are reunited
/** Converts the shell version of SplitBounds to the launcher version */
@JvmStatic
fun convertShellSplitBoundsToLauncher(
shellSplitBounds: SplitBounds?
): SplitConfigurationOptions.SplitBounds? {
return if (shellSplitBounds == null) {
null
} else {
SplitConfigurationOptions.SplitBounds(
shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId,
shellSplitBounds.snapPosition
)
}
}
/** Converts the launcher version of SplitBounds to the shell version */
@JvmStatic
fun convertLauncherSplitBoundsToShell(
launcherSplitBounds: SplitConfigurationOptions.SplitBounds?
): SplitBounds? {
return if (launcherSplitBounds == null) {
null
} else {
SplitBounds(
launcherSplitBounds.leftTopBounds,
launcherSplitBounds.rightBottomBounds,
launcherSplitBounds.leftTopTaskId,
launcherSplitBounds.rightBottomTaskId,
launcherSplitBounds.snapPosition
)
}
}
}
}
@@ -22,7 +22,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK;
@@ -33,6 +32,7 @@ import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_TASK
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -98,6 +98,7 @@ import com.android.quickstep.views.SplitInstructionsView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
import java.io.PrintWriter;
@@ -288,11 +289,11 @@ public class SplitSelectStateController {
* To be called when the both split tasks are ready to be launched. Call after launcher side
* animations are complete.
*/
public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
public void launchSplitTasks(@SnapPosition int snapPosition,
@Nullable Consumer<Boolean> callback) {
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
launchTasks(callback, false /* freezeTaskList */, DEFAULT_SPLIT_RATIO,
instanceIds.first);
launchTasks(callback, false /* freezeTaskList */, snapPosition, instanceIds.first);
mStatsLogManager.logger()
.withItemInfo(mSplitSelectDataHolder.getItemInfo())
@@ -301,11 +302,18 @@ public class SplitSelectStateController {
}
/**
* A version of {@link #launchTasks(Consumer, boolean, float, InstanceId)} with no success
* callback.
* A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
*/
public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
launchSplitTasks(SNAP_TO_50_50, callback);
}
/**
* A version of {@link #launchSplitTasks(int, Consumer)} that launches with a default split
* ratio and no callback.
*/
public void launchSplitTasks() {
launchSplitTasks(null);
launchSplitTasks(SNAP_TO_50_50, null);
}
/**
@@ -342,7 +350,7 @@ public class SplitSelectStateController {
* foreground (quickswitch, launching previous pairs from overview)
*/
public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
float splitRatio, @Nullable InstanceId shellInstanceId) {
@SnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
final ActivityOptions options1 = ActivityOptions.makeBasic();
@@ -369,33 +377,33 @@ public class SplitSelectStateController {
switch (launchData.getSplitLaunchType()) {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, initialStagePosition, splitRatio,
null /* options2 */, initialStagePosition, snapPosition,
remoteTransition, shellInstanceId);
case SPLIT_TASK_PENDINGINTENT ->
mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
firstTaskId, null /*options2*/, initialStagePosition, snapPosition,
remoteTransition, shellInstanceId);
case SPLIT_TASK_SHORTCUT ->
mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
firstTaskId, null /*options2*/, initialStagePosition, snapPosition,
remoteTransition, shellInstanceId);
case SPLIT_PENDINGINTENT_TASK ->
mSystemUiProxy.startIntentAndTask(firstPI, firstUserId, optionsBundle,
secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
secondTaskId, null /*options2*/, initialStagePosition, snapPosition,
remoteTransition, shellInstanceId);
case SPLIT_PENDINGINTENT_PENDINGINTENT ->
mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
optionsBundle, secondPI, secondUserId, secondShortcut,
null /*options2*/, initialStagePosition, splitRatio,
null /*options2*/, initialStagePosition, snapPosition,
remoteTransition, shellInstanceId);
case SPLIT_SHORTCUT_TASK ->
mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
secondTaskId, null /*options2*/, initialStagePosition, snapPosition,
remoteTransition, shellInstanceId);
}
} else {
@@ -405,40 +413,40 @@ public class SplitSelectStateController {
case SPLIT_TASK_TASK ->
mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
secondTaskId, null /* options2 */, initialStagePosition,
splitRatio, adapter, shellInstanceId);
snapPosition, adapter, shellInstanceId);
case SPLIT_TASK_PENDINGINTENT ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
secondUserId, optionsBundle, firstTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
initialStagePosition, snapPosition, adapter, shellInstanceId);
case SPLIT_TASK_SHORTCUT ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
splitRatio, adapter, shellInstanceId);
snapPosition, adapter, shellInstanceId);
case SPLIT_PENDINGINTENT_TASK ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
initialStagePosition, snapPosition, adapter, shellInstanceId);
case SPLIT_PENDINGINTENT_PENDINGINTENT ->
mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstUserId,
firstShortcut, optionsBundle, secondPI, secondUserId,
secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
adapter, shellInstanceId);
secondShortcut, null /*options2*/, initialStagePosition,
snapPosition, adapter, shellInstanceId);
case SPLIT_SHORTCUT_TASK ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, splitRatio, adapter, shellInstanceId);
initialStagePosition, snapPosition, adapter, shellInstanceId);
}
}
}
/**
* Used to launch split screen from a split pair that already exists (usually accessible through
* Overview). This is different than {@link #launchTasks(Consumer, boolean, float, InstanceId)}
* Overview). This is different than {@link #launchTasks(Consumer, boolean, int, InstanceId)}
* in that this only launches split screen that are existing tasks. This doesn't determine which
* API should be used (i.e. launching split with existing tasks vs intents vs shortcuts, etc).
*
@@ -447,7 +455,7 @@ public class SplitSelectStateController {
*/
public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
Consumer<Boolean> callback, boolean freezeTaskList, @SnapPosition int snapPosition) {
mLaunchingTaskView = groupedTaskView;
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
@@ -458,21 +466,20 @@ public class SplitSelectStateController {
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
secondTaskId, callback, "LaunchExistingPair");
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, stagePosition, splitRatio,
remoteTransition, null /*shellInstanceId*/);
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
stagePosition, snapPosition, remoteTransition, null /*shellInstanceId*/);
} else {
final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
secondTaskId, callback);
mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
secondTaskId, null /* options2 */, stagePosition,
splitRatio, adapter, null /*shellInstanceId*/);
mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle, secondTaskId,
null /* options2 */, stagePosition, snapPosition, adapter,
null /*shellInstanceId*/);
}
}
/**
* Launches the initially selected task/intent in fullscreen (note the same SystemUi APIs are
* used as {@link #launchSplitTasks(Consumer)} because they are overloaded to launch both
* used as {@link #launchSplitTasks(int, Consumer)} because they are overloaded to launch both
* split and fullscreen tasks)
*/
public void launchInitialAppFullscreen(Consumer<Boolean> callback) {
@@ -497,14 +504,13 @@ public class SplitSelectStateController {
switch (launchData.getSplitLaunchType()) {
case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId,
optionsBundle, secondTaskId, null /* options2 */, initialStagePosition,
DEFAULT_SPLIT_RATIO, remoteTransition, instanceId);
SNAP_TO_50_50, remoteTransition, instanceId);
case SPLIT_SINGLE_INTENT_FULLSCREEN -> mSystemUiProxy.startIntentAndTask(firstPI,
firstUserId, optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, DEFAULT_SPLIT_RATIO, remoteTransition,
instanceId);
initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
case SPLIT_SINGLE_SHORTCUT_FULLSCREEN -> mSystemUiProxy.startShortcutAndTask(
initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
initialStagePosition, DEFAULT_SPLIT_RATIO, remoteTransition, instanceId);
initialStagePosition, SNAP_TO_50_50, remoteTransition, instanceId);
}
} else {
final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
@@ -512,16 +518,15 @@ public class SplitSelectStateController {
switch (launchData.getSplitLaunchType()) {
case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasksWithLegacyTransition(
firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
initialStagePosition, DEFAULT_SPLIT_RATIO, adapter, instanceId);
initialStagePosition, SNAP_TO_50_50, adapter, instanceId);
case SPLIT_SINGLE_INTENT_FULLSCREEN ->
mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI, firstUserId,
optionsBundle, secondTaskId, null /*options2*/,
initialStagePosition, DEFAULT_SPLIT_RATIO, adapter,
instanceId);
initialStagePosition, SNAP_TO_50_50, adapter, instanceId);
case SPLIT_SINGLE_SHORTCUT_FULLSCREEN ->
mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(
initialShortcut, optionsBundle, firstTaskId, null /* options2 */,
initialStagePosition, DEFAULT_SPLIT_RATIO, adapter, instanceId);
initialStagePosition, SNAP_TO_50_50, adapter, instanceId);
}
}
}
@@ -26,6 +26,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
import android.animation.TimeInterpolator;
import android.content.Context;
@@ -210,7 +211,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
mStagePosition = mThumbnailPosition.equals(splitInfo.leftTopBounds) ?
STAGE_POSITION_TOP_OR_LEFT :
STAGE_POSITION_BOTTOM_OR_RIGHT;
mPositionHelper.setSplitBounds(convertSplitBounds(mSplitBounds), mStagePosition);
mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds),
mStagePosition);
}
/**
@@ -435,16 +437,4 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
// Ideally we should use square-root. This is an optimization as one of the dimension is 0.
return Math.max(Math.abs(mTempPoint[0]), Math.abs(mTempPoint[1]));
}
/**
* TODO(b/254378592): Remove this after consolidation of classes
*/
public static com.android.wm.shell.util.SplitBounds convertSplitBounds(SplitBounds bounds) {
return new com.android.wm.shell.util.SplitBounds(
bounds.leftTopBounds,
bounds.rightBottomBounds,
bounds.leftTopTaskId,
bounds.rightBottomTaskId
);
}
}
@@ -2,8 +2,8 @@ package com.android.quickstep.views;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
import android.content.Context;
import android.graphics.PointF;
@@ -28,11 +28,11 @@ import com.android.quickstep.TaskThumbnailCache;
import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
import java.util.HashMap;
import java.util.function.Consumer;
@@ -114,11 +114,11 @@ public class GroupedTaskView extends TaskView {
if (mSplitBoundsConfig == null) {
return;
}
mSnapshotView.getPreviewPositionHelper().setSplitBounds(TaskViewSimulator
.convertSplitBounds(splitBoundsConfig),
mSnapshotView.getPreviewPositionHelper().setSplitBounds(
convertLauncherSplitBoundsToShell(splitBoundsConfig),
PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT);
mSnapshotView2.getPreviewPositionHelper().setSplitBounds(TaskViewSimulator
.convertSplitBounds(splitBoundsConfig),
mSnapshotView2.getPreviewPositionHelper().setSplitBounds(
convertLauncherSplitBoundsToShell(splitBoundsConfig),
PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT);
}
@@ -180,12 +180,20 @@ public class GroupedTaskView extends TaskView {
invalidate();
}
public float getSplitRatio() {
if (mSplitBoundsConfig != null) {
return mSplitBoundsConfig.appsStackedVertically
? mSplitBoundsConfig.topTaskPercent : mSplitBoundsConfig.leftTaskPercent;
@Nullable
public SplitBounds getSplitBoundsConfig() {
return mSplitBoundsConfig;
}
/**
* Returns the {@link SnapPosition} of this pair of tasks.
*/
public int getSnapPosition() {
if (mSplitBoundsConfig == null) {
throw new IllegalStateException("mSplitBoundsConfig is null");
}
return DEFAULT_SPLIT_RATIO;
return mSplitBoundsConfig.snapPosition;
}
@Override
@@ -251,7 +259,7 @@ public class GroupedTaskView extends TaskView {
getRecentsView().getSplitSelectController().launchExistingSplitPair(
launchingExistingTaskView ? this : null, mTask.key.id,
mSecondaryTask.key.id, SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
callback, isQuickswitch, getSplitRatio());
callback, isQuickswitch, getSnapPosition());
}
@Override
@@ -20,7 +20,6 @@ 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
import android.graphics.Rect
import android.os.Handler
@@ -38,7 +37,7 @@ import com.android.launcher3.util.withArgCaptor
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.systemui.shared.recents.model.Task
import java.util.function.Consumer
import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -50,6 +49,7 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
class SplitSelectStateControllerTest {
@@ -560,7 +560,7 @@ class SplitSelectStateControllerTest {
return GroupTask(
task1,
task2,
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1)
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50)
)
}
@@ -592,7 +592,7 @@ class SplitSelectStateControllerTest {
return GroupTask(
task1,
task2,
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1)
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_50_50)
)
}
}
@@ -77,11 +77,6 @@ public final class SplitConfigurationOptions {
public @interface StageType {}
///////////////////////////////////
/**
* Default split ratio for launching app pair from overview.
*/
public static final float DEFAULT_SPLIT_RATIO = 0.5f;
public static class SplitPositionOption {
public final int iconResId;
public final int textResId;
@@ -116,6 +111,8 @@ public final class SplitConfigurationOptions {
public final float leftTaskPercent;
public final float dividerWidthPercent;
public final float dividerHeightPercent;
public final int snapPosition;
/**
* If {@code true}, that means at the time of creation of this object, the
* split-screened apps were vertically stacked. This is useful in scenarios like
@@ -135,11 +132,12 @@ public final class SplitConfigurationOptions {
public final int rightBottomTaskId;
public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
int rightBottomTaskId) {
int rightBottomTaskId, int snapPosition) {
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
this.leftTopTaskId = leftTopTaskId;
this.rightBottomTaskId = rightBottomTaskId;
this.snapPosition = snapPosition;
if (rightBottomBounds.top > leftTopBounds.top) {
// vertical apps, horizontal divider