From 95c85034ead5468588f8b642260d93519bfdbc98 Mon Sep 17 00:00:00 2001 From: mattsziklay Date: Wed, 5 Jul 2023 09:26:14 -0700 Subject: [PATCH] Allow split select transition from desktop mode. Introduces a new controller to manage an app entering split select mode from desktop mode. Video: http://recall/-/gjymLwjdDT07aWqaK6101a/gU56zTDcWov6ukbKuH8tFx Flag: ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE Bug: 286550932 Bug: 279586624 Test: Manual Change-Id: Ib94402553c88286894d94c95c38cac125be23a0d --- .../uioverrides/QuickstepLauncher.java | 2 + .../com/android/quickstep/SystemUiProxy.java | 36 +++++ .../util/SplitSelectStateController.java | 142 ++++++++++++++++++ .../util/SplitToWorkspaceController.java | 4 +- .../launcher3/config/FeatureFlags.java | 4 + .../launcher3/logging/StatsLogManager.java | 3 + 6 files changed, 190 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 45d2fb0c38..08ce7943ba 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -33,6 +33,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; +import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE; import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE; import static com.android.launcher3.config.FeatureFlags.RECEIVE_UNFOLD_EVENTS_FROM_SYSUI; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; @@ -261,6 +262,7 @@ public class QuickstepLauncher extends Launcher { mDesktopVisibilityController = new DesktopVisibilityController(this); if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) { mDesktopVisibilityController.registerSystemUiListener(); + mSplitSelectStateController.initSplitFromDesktopController(this); } mHotseatPredictionController = new HotseatPredictionController(this); diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index c9b7d5ecdd..e73b52585f 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -84,6 +84,7 @@ import com.android.wm.shell.recents.IRecentTasks; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.splitscreen.ISplitScreen; import com.android.wm.shell.splitscreen.ISplitScreenListener; +import com.android.wm.shell.splitscreen.ISplitSelectListener; import com.android.wm.shell.startingsurface.IStartingWindow; import com.android.wm.shell.startingsurface.IStartingWindowListener; import com.android.wm.shell.transition.IShellTransitions; @@ -128,6 +129,7 @@ public class SystemUiProxy implements ISystemUiProxy { private IPipAnimationListener mPipAnimationListener; private IBubblesListener mBubblesListener; private ISplitScreenListener mSplitScreenListener; + private ISplitSelectListener mSplitSelectListener; private IStartingWindowListener mStartingWindowListener; private ILauncherUnlockAnimationController mLauncherUnlockAnimationController; private IRecentTasksListener mRecentTasksListener; @@ -239,6 +241,7 @@ public class SystemUiProxy implements ISystemUiProxy { setPipAnimationListener(mPipAnimationListener); setBubblesListener(mBubblesListener); registerSplitScreenListener(mSplitScreenListener); + registerSplitSelectListener(mSplitSelectListener); setStartingWindowListener(mStartingWindowListener); setLauncherUnlockAnimationController(mLauncherUnlockAnimationController); new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition); @@ -740,6 +743,28 @@ public class SystemUiProxy implements ISystemUiProxy { mSplitScreenListener = null; } + public void registerSplitSelectListener(ISplitSelectListener listener) { + if (mSplitScreen != null) { + try { + mSplitScreen.registerSplitSelectListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call registerSplitSelectListener"); + } + } + mSplitSelectListener = listener; + } + + public void unregisterSplitSelectListener(ISplitSelectListener listener) { + if (mSplitScreen != null) { + try { + mSplitScreen.unregisterSplitSelectListener(listener); + } catch (RemoteException e) { + Log.w(TAG, "Failed call unregisterSplitSelectListener"); + } + } + mSplitSelectListener = null; + } + /** Start multiple tasks in split-screen simultaneously. */ public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2, @SplitConfigurationOptions.StagePosition int splitPosition, float splitRatio, @@ -1281,6 +1306,17 @@ public class SystemUiProxy implements ISystemUiProxy { } } + /** Perform cleanup transactions after animation to split select is complete */ + public void onDesktopSplitSelectAnimComplete(ActivityManager.RunningTaskInfo taskInfo) { + if (mDesktopMode != null) { + try { + mDesktopMode.onDesktopSplitSelectAnimComplete(taskInfo); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onDesktopSplitSelectAnimComplete", e); + } + } + } + // // Unfold transition // diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 453a1bdf13..5565139a1e 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -17,10 +17,14 @@ package com.android.quickstep.util; import static com.android.launcher3.Utilities.postAsyncCallback; +import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM; import static com.android.launcher3.testing.shared.TestProtocol.LAUNCH_SPLIT_PAIR; import static com.android.launcher3.testing.shared.TestProtocol.testLogD; 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; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK; @@ -31,6 +35,8 @@ import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDIN import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT; import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -38,11 +44,16 @@ import android.app.ActivityThread; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; import android.util.Pair; @@ -57,7 +68,11 @@ import android.window.TransitionInfo; import androidx.annotation.Nullable; import com.android.internal.logging.InstanceId; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.icons.IconProvider; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.statehandlers.DepthController; @@ -66,6 +81,11 @@ import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; +import com.android.quickstep.OverviewComponentObserver; +import com.android.quickstep.RecentsAnimationCallbacks; +import com.android.quickstep.RecentsAnimationController; +import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; import com.android.quickstep.SplitSelectionListener; import com.android.quickstep.SystemUiProxy; @@ -74,8 +94,11 @@ import com.android.quickstep.TaskViewUtils; import com.android.quickstep.views.FloatingTaskView; import com.android.quickstep.views.GroupedTaskView; import com.android.quickstep.views.SplitInstructionsView; +import com.android.quickstep.views.RecentsView; 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.splitscreen.ISplitSelectListener; import java.io.PrintWriter; import java.util.ArrayList; @@ -99,6 +122,7 @@ public class SplitSelectStateController { private final StatsLogManager mStatsLogManager; private final SystemUiProxy mSystemUiProxy; private final StateManager mStateManager; + private SplitFromDesktopController mSplitFromDesktopController; @Nullable private DepthController mDepthController; private boolean mRecentsAnimationRunning; @@ -476,6 +500,14 @@ public class SplitSelectStateController { } } + public void initSplitFromDesktopController(Launcher launcher) { + mSplitFromDesktopController = new SplitFromDesktopController(launcher); + } + + public void enterSplitFromDesktop(ActivityManager.RunningTaskInfo taskInfo) { + mSplitFromDesktopController.enterSplitSelect(taskInfo); + } + private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId, @Nullable Consumer callback, String transitionName) { final RemoteSplitLaunchTransitionRunner animationRunner = @@ -686,4 +718,114 @@ public class SplitSelectStateController { mSplitSelectDataHolder.dump(prefix, writer); } } + + public class SplitFromDesktopController { + private static final String TAG = "SplitFromDesktopController"; + + private final Launcher mLauncher; + private final OverviewComponentObserver mOverviewComponentObserver; + private final int mSplitPlaceholderSize; + private final int mSplitPlaceholderInset; + private ActivityManager.RunningTaskInfo mTaskInfo; + private ISplitSelectListener mSplitSelectListener; + private Drawable mAppIcon; + + public SplitFromDesktopController(Launcher launcher) { + mLauncher = launcher; + RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState( + launcher.getApplicationContext()); + mOverviewComponentObserver = + new OverviewComponentObserver(launcher.getApplicationContext(), deviceState); + mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize( + R.dimen.split_placeholder_size); + mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize( + R.dimen.split_placeholder_inset); + mSplitSelectListener = new ISplitSelectListener.Stub() { + @Override + public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo) { + if (!ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get()) return false; + MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo)); + return true; + } + }; + SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener); + } + + /** + * Enter split select from desktop mode. + * @param taskInfo the desktop task to move to split stage + */ + public void enterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) { + mTaskInfo = taskInfo; + String packageName = mTaskInfo.realActivity.getPackageName(); + PackageManager pm = mLauncher.getApplicationContext().getPackageManager(); + IconProvider provider = new IconProvider(mLauncher.getApplicationContext()); + try { + mAppIcon = provider.getIcon(pm.getActivityInfo(mTaskInfo.baseActivity, + PackageManager.ComponentInfoFlags.of(0))); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Package not found: " + packageName, e); + } + RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks( + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()), + false /* allowMinimizeSplitScreen */); + + DesktopSplitRecentsAnimationListener listener = + new DesktopSplitRecentsAnimationListener(); + + MAIN_EXECUTOR.execute(() -> { + callbacks.addListener(listener); + UI_HELPER_EXECUTOR.execute( + // Transition from app to enter stage split in launcher with + // recents animation. + () -> ActivityManagerWrapper.getInstance().startRecentsActivity( + mOverviewComponentObserver.getOverviewIntent(), + SystemClock.uptimeMillis(), callbacks, null, null)); + }); + } + + private class DesktopSplitRecentsAnimationListener implements + RecentsAnimationCallbacks.RecentsAnimationListener { + private final Rect mTempRect = new Rect(); + + @Override + public void onRecentsAnimationStart(RecentsAnimationController controller, + RecentsAnimationTargets targets) { + setInitialTaskSelect(mTaskInfo, STAGE_POSITION_BOTTOM_OR_RIGHT, + null, LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM); + + RecentsView recentsView = mLauncher.getOverviewPanel(); + recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds( + mSplitPlaceholderSize, mSplitPlaceholderInset, + mLauncher.getDeviceProfile(), getActiveSplitStagePosition(), mTempRect); + + PendingAnimation anim = new PendingAnimation( + SplitAnimationTimings.TABLET_HOME_TO_SPLIT.getDuration()); + RectF startingTaskRect = new RectF(mTaskInfo.configuration.windowConfiguration + .getBounds()); + final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView( + mLauncher, mLauncher.getDragLayer(), + null /* thumbnail */, + mAppIcon, new RectF()); + floatingTaskView.setAlpha(1); + floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, + false /* fadeWithThumbnail */, true /* isStagedTask */); + setFirstFloatingTaskView(floatingTaskView); + + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + controller.finish(true /* toRecents */, null /* onFinishComplete */, + false /* sendUserLeaveHint */); + } + @Override + public void onAnimationEnd(Animator animation) { + SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()) + .onDesktopSplitSelectAnimComplete(mTaskInfo); + } + }); + anim.buildAnim().start(); + } + } + } } diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java index b36cf5f5b4..056f9aa4a7 100644 --- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java +++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; +import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE; import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS; import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE; @@ -178,7 +179,8 @@ public class SplitToWorkspaceController { private boolean shouldIgnoreSecondSplitLaunch() { return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get() - && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) + && !ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() + && !ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE.get()) || !mController.isSplitSelectActive(); } } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 273d5051a2..39d5f9a49a 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -370,6 +370,10 @@ public final class FeatureFlags { 270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", TEAMFOOD, "Enable initiating split screen from workspace to workspace."); + public static final BooleanFlag ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE = getDebugFlag( + 279586624, "ENABLE_SPLIT_FROM_DESKTOP_TO_WORKSPACE", DISABLED, + "Enable initiating split screen from desktop mode to workspace."); + public static final BooleanFlag ENABLE_TRACKPAD_GESTURE = getDebugFlag(271010401, "ENABLE_TRACKPAD_GESTURE", ENABLED, "Enables trackpad gesture."); diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 8bb06c1b99..780cb5e990 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -621,6 +621,9 @@ public class StatsLogManager implements ResourceBasedOverride { @UiEvent(doc = "User has invoked split to left half with a keyboard shortcut.") LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP(1233), + @UiEvent(doc = "User has invoked split to right half with desktop mode app icon") + LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM(1412), + @UiEvent(doc = "User has collapsed the work FAB button by scrolling down in the all apps" + " work A-Z list.") LAUNCHER_WORK_FAB_BUTTON_COLLAPSE(1276),