From 5bc6a0aa1f6ff8a97b7b2e4875959533aa7f9a47 Mon Sep 17 00:00:00 2001 From: Schneider Victor-tulias Date: Mon, 8 Jan 2024 15:00:54 -0500 Subject: [PATCH] Update KQS task launch animation Updated the KQS app launch animation to match the spec: translate the launching app from the left (right in RTL) and translate the closing app to the right (left in RTL) Flag: LEGACY ENABLE_KEYBOARD_QUICK_SWITCH ENABLED Fixes: 313606549 Fixes: 313607264 Test: launched tasks from KQS from home and launched app Change-Id: I0e903c741e4f930b377607b7eaf42a87177c3eb6 --- .../KeyboardQuickSwitchViewController.java | 24 ++-- .../taskbar/LauncherTaskbarUIController.java | 6 +- .../taskbar/TaskbarUIController.java | 4 +- .../uioverrides/QuickstepLauncher.java | 7 +- .../quickstep/util/SlideInRemoteTransition.kt | 115 ++++++++++++++++++ .../util/SplitSelectStateController.java | 33 ++++- 6 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index 6e88780ef0..9dac89dcd1 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -15,20 +15,26 @@ */ package com.android.launcher3.taskbar; +import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; + import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.animation.Animator; +import android.app.ActivityOptions; import android.view.KeyEvent; import android.view.View; +import android.window.RemoteTransition; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.util.GroupTask; +import com.android.quickstep.util.SlideInRemoteTransition; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -142,19 +148,23 @@ public class KeyboardQuickSwitchViewController { return -1; } + RemoteTransition remoteTransition = new RemoteTransition(new SlideInRemoteTransition( + Utilities.isRtl(mControllers.taskbarActivityContext.getResources()))); if (mOnDesktop) { UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext()) .showDesktopApp(task.task1.key.id)); } else if (task.task2 == null) { - UI_HELPER_EXECUTOR.execute(() -> - ActivityManagerWrapper.getInstance().startActivityFromRecents( - task.task1.key, - mControllers.taskbarActivityContext.getActivityLaunchOptions( - taskView == null ? mKeyboardQuickSwitchView : taskView, null) - .options)); + UI_HELPER_EXECUTOR.execute(() -> { + ActivityOptions activityOptions = mControllers.taskbarActivityContext + .makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options; + activityOptions.setRemoteTransition(remoteTransition); + + ActivityManagerWrapper.getInstance().startActivityFromRecents( + task.task1.key, activityOptions); + }); } else { - mControllers.uiController.launchSplitTasks(task); + mControllers.uiController.launchSplitTasks(task, remoteTransition); } return -1; } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 159a6eff07..d62e11721e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -27,6 +27,7 @@ import android.os.RemoteException; import android.util.Log; import android.view.TaskTransitionSpec; import android.view.WindowManagerGlobal; +import android.window.RemoteTransition; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -390,8 +391,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController { } @Override - public void launchSplitTasks(@NonNull GroupTask groupTask) { - mLauncher.launchSplitTasks(groupTask); + public void launchSplitTasks( + @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { + mLauncher.launchSplitTasks(groupTask, remoteTransition); } @Override diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index 7edf0d3208..ecedf8ad60 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.graphics.drawable.BitmapDrawable; import android.view.MotionEvent; import android.view.View; +import android.window.RemoteTransition; import androidx.annotation.CallSuper; import androidx.annotation.NonNull; @@ -302,7 +303,8 @@ public class TaskbarUIController { /** * Launches the given task in split-screen. */ - public void launchSplitTasks(@NonNull GroupTask groupTask) { } + public void launchSplitTasks( + @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { } /** * Returns the matching view (if any) in the taskbar. diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index f45ddb6754..71f4faf56c 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -84,6 +84,7 @@ import android.widget.TextClock; import android.window.BackEvent; import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedDispatcher; +import android.window.RemoteTransition; import android.window.SplashScreen; import androidx.annotation.BinderThread; @@ -1271,7 +1272,8 @@ public class QuickstepLauncher extends Launcher { /** * Launches the given {@link GroupTask} in splitscreen. */ - public void launchSplitTasks(@NonNull GroupTask groupTask) { + public void launchSplitTasks( + @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { // Top/left and bottom/right tasks respectively. Task task1 = groupTask.task1; // task2 should never be null when calling this method. Allow a crash to catch invalid calls @@ -1285,7 +1287,8 @@ public class QuickstepLauncher extends Launcher { /* freezeTaskList= */ false, groupTask.mSplitBounds == null ? SNAP_TO_50_50 - : groupTask.mSplitBounds.snapPosition); + : groupTask.mSplitBounds.snapPosition, + remoteTransition); } /** diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt new file mode 100644 index 0000000000..c1fa2f3e25 --- /dev/null +++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt @@ -0,0 +1,115 @@ +/* + * 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 android.animation.ValueAnimator +import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME +import android.graphics.Rect +import android.os.IBinder +import android.os.RemoteException +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.window.IRemoteTransition +import android.window.IRemoteTransitionFinishedCallback +import android.window.TransitionInfo +import com.android.launcher3.anim.AnimatorListeners.forEndCallback +import com.android.launcher3.util.Executors +import com.android.wm.shell.util.TransitionUtil + +/** Remote animation which slides the opening targets in and the closing targets out */ +class SlideInRemoteTransition(val isRtl: Boolean) : IRemoteTransition.Stub() { + + override fun mergeAnimation( + iBinder: IBinder, + transitionInfo: TransitionInfo, + transaction: Transaction, + mergeTarget: IBinder, + finishCB: IRemoteTransitionFinishedCallback + ) { + + try { + finishCB.onTransitionFinished(null, Transaction()) + } catch (e: RemoteException) { + // Ignore + } + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startT: Transaction, + finishCB: IRemoteTransitionFinishedCallback + ) { + val anim = ValueAnimator.ofFloat(0f, 1f) + + val closingStartBounds: HashMap = HashMap() + val openingEndBounds: HashMap = HashMap() + for (chg in info.changes) { + val leash = chg.leash + startT.show(leash) + + val taskInfo = chg.taskInfo + if (taskInfo?.activityType == ACTIVITY_TYPE_HOME || taskInfo?.parentTaskId != -1) { + continue + } + if (TransitionUtil.isClosingType(chg.mode)) { + closingStartBounds[leash] = chg.startAbsBounds + } + if (TransitionUtil.isOpeningType(chg.mode)) { + openingEndBounds[leash] = chg.endAbsBounds + } + } + startT.apply() + + anim.addUpdateListener { + val t = Transaction() + closingStartBounds.keys.forEach { + // Translate the surface from its original position on-screen to off-screen on the + // right (or left in RTL) + val startBounds = closingStartBounds[it] + val targetX = (if (isRtl) -1 else 1) * startBounds!!.right + t.setPosition(it, anim.animatedValue as Float * targetX, 0f) + } + openingEndBounds.keys.forEach { + // Set the alpha in the update listener to prevent one visible frame at the + // beginning + t.setAlpha(it, 1f) + // Translate the surface from off-screen on the left (or left in RTL) to its final + // position on-screen + val endBounds = openingEndBounds[it] + val targetX = (if (isRtl) -1 else 1) * endBounds!!.right + t.setPosition(it, (1f - anim.animatedValue as Float) * -targetX, 0f) + } + t.apply() + } + anim.addListener( + forEndCallback( + Runnable { + val t = Transaction() + try { + finishCB.onTransitionFinished(null, t) + } catch (e: RemoteException) { + // Ignore + } + } + ) + ) + + Executors.MAIN_EXECUTOR.execute { anim.start() } + } + + override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {} +} diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java index 193cc2b732..3c90e0cf51 100644 --- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java +++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java @@ -497,6 +497,29 @@ public class SplitSelectStateController { } } + /** + * Used to launch split screen from a split pair that already exists, optionally with a custom + * remote transition. + *

+ * See {@link SplitSelectStateController#launchExistingSplitPair( + * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)} + */ + public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, + int firstTaskId, int secondTaskId, @StagePosition int stagePosition, + Consumer callback, boolean freezeTaskList, + @PersistentSnapPosition int snapPosition) { + launchExistingSplitPair( + groupedTaskView, + firstTaskId, + secondTaskId, + stagePosition, + callback, + freezeTaskList, + snapPosition, + /* remoteTransition= */ null); + } + + /** * Used to launch split screen from a split pair that already exists (usually accessible through * Overview). This is different than {@link #launchTasks(Consumer, boolean, int, InstanceId)} @@ -509,7 +532,7 @@ public class SplitSelectStateController { public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView, int firstTaskId, int secondTaskId, @StagePosition int stagePosition, Consumer callback, boolean freezeTaskList, - @PersistentSnapPosition int snapPosition) { + @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) { mLaunchingTaskView = groupedTaskView; final ActivityOptions options1 = ActivityOptions.makeBasic(); if (freezeTaskList) { @@ -518,10 +541,12 @@ public class SplitSelectStateController { Bundle optionsBundle = options1.toBundle(); if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) { - final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId, - secondTaskId, callback, "LaunchExistingPair"); + final RemoteTransition transition = remoteTransition == null + ? getShellRemoteTransition( + firstTaskId, secondTaskId, callback, "LaunchExistingPair") + : remoteTransition; mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */, - stagePosition, snapPosition, remoteTransition, null /*shellInstanceId*/); + stagePosition, snapPosition, transition, null /*shellInstanceId*/); } else { final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId, secondTaskId, callback);