diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt index 6916a1d26a..e160f8293d 100644 --- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt +++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt @@ -20,6 +20,7 @@ import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context +import android.graphics.Rect import android.os.IBinder import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_OPEN @@ -31,6 +32,7 @@ import android.window.TransitionInfo import android.window.TransitionInfo.Change import androidx.core.animation.addListener import com.android.app.animation.Interpolators +import com.android.internal.policy.ScreenDecorationsUtils import com.android.quickstep.RemoteRunnable import com.android.wm.shell.shared.animation.MinimizeAnimator import com.android.wm.shell.shared.animation.WindowAnimator @@ -43,8 +45,19 @@ import java.util.concurrent.Executor * ([android.view.WindowManager.TRANSIT_TO_BACK]) this transition will apply a minimize animation to * that window. */ -class DesktopAppLaunchTransition(private val context: Context, private val mainExecutor: Executor) : - RemoteTransitionStub() { +class DesktopAppLaunchTransition( + private val context: Context, + private val mainExecutor: Executor, + private val launchType: AppLaunchType, +) : RemoteTransitionStub() { + + enum class AppLaunchType( + val boundsAnimationParams: WindowAnimator.BoundsAnimationParams, + val alphaDurationMs: Long, + ) { + LAUNCH(launchBoundsAnimationDef, /* alphaDurationMs= */ 200L), + UNMINIMIZE(unminimizeBoundsAnimationDef, /* alphaDurationMs= */ 100L), + } override fun startAnimation( token: IBinder, @@ -105,18 +118,24 @@ class DesktopAppLaunchTransition(private val context: Context, private val mainE val boundsAnimator = WindowAnimator.createBoundsAnimator( context.resources.displayMetrics, - launchBoundsAnimationDef, + launchType.boundsAnimationParams, change, transaction, ) val alphaAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - duration = LAUNCH_ANIM_ALPHA_DURATION_MS + duration = launchType.alphaDurationMs interpolator = Interpolators.LINEAR addUpdateListener { animation -> transaction.setAlpha(change.leash, animation.animatedValue as Float).apply() } } + val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) } + transaction.setCrop(change.leash, clipRect) + transaction.setCornerRadius( + change.leash, + ScreenDecorationsUtils.getWindowCornerRadius(context), + ) return AnimatorSet().apply { playTogether(boundsAnimator, alphaAnimator) addListener(onEnd = { animation -> onAnimFinish(animation) }) @@ -124,12 +143,17 @@ class DesktopAppLaunchTransition(private val context: Context, private val mainE } companion object { - private val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT) - - private const val LAUNCH_ANIM_ALPHA_DURATION_MS = 100L - private const val MINIMIZE_ANIM_ALPHA_DURATION_MS = 100L + val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT) private val launchBoundsAnimationDef = + WindowAnimator.BoundsAnimationParams( + durationMs = 600, + startOffsetYDp = 36f, + startScale = 0.95f, + interpolator = Interpolators.STANDARD_DECELERATE, + ) + + private val unminimizeBoundsAnimationDef = WindowAnimator.BoundsAnimationParams( durationMs = 300, startOffsetYDp = 12f, diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java index 5e116012d5..390112e676 100644 --- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java @@ -36,6 +36,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.desktop.DesktopAppLaunchTransition; +import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType; import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext; import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer; import com.android.launcher3.util.DisplayController; @@ -251,7 +252,8 @@ public class KeyboardQuickSwitchViewController { ) { // This app is being unminimized - use our own transition runner. remoteTransition = new RemoteTransition( - new DesktopAppLaunchTransition(context, MAIN_EXECUTOR)); + new DesktopAppLaunchTransition( + context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE)); } mControllers.taskbarActivityContext.handleGroupTaskLaunch( task, diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 5b9381cd8e..82acc0ce6a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -59,6 +59,7 @@ import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.display.DisplayManager; +import android.os.Bundle; import android.os.IRemoteCallback; import android.os.Process; import android.os.Trace; @@ -91,6 +92,7 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.apppairs.AppPairIcon; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.desktop.DesktopAppLaunchTransition; +import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; @@ -862,6 +864,33 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED); } + private ActivityOptionsWrapper getActivityLaunchDesktopOptions(ItemInfo info) { + if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue()) { + return null; + } + if (!areDesktopTasksVisible()) { + return null; + } + BubbleTextView.RunningAppState appState = + mControllers.taskbarRecentAppsController.getDesktopItemState(info); + AppLaunchType launchType = null; + switch (appState) { + case RUNNING: + return null; + case MINIMIZED: + launchType = AppLaunchType.UNMINIMIZE; + break; + case NOT_RUNNING: + launchType = AppLaunchType.LAUNCH; + break; + } + ActivityOptions options = ActivityOptions.makeRemoteTransition( + new RemoteTransition( + new DesktopAppLaunchTransition( + /* context= */ this, getMainExecutor(), launchType))); + return new ActivityOptionsWrapper(options, new RunnableList()); + } + /** * Sets a new data-source for this taskbar instance */ @@ -1402,7 +1431,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext { } private RemoteTransition createUnminimizeRemoteTransition() { - return new RemoteTransition(new DesktopAppLaunchTransition(this, getMainExecutor())); + return new RemoteTransition( + new DesktopAppLaunchTransition( + this, getMainExecutor(), AppLaunchType.UNMINIMIZE)); } /** @@ -1503,25 +1534,31 @@ public class TaskbarActivityContext extends BaseTaskbarContext { .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon"); - if (info.user.equals(Process.myUserHandle())) { - // TODO(b/216683257): Use startActivityForResult for search results that require it. - if (taskInRecents != null) { - // Re launch instance from recents - ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info); - opts.options.setLaunchDisplayId( - getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); - if (ActivityManagerWrapper.getInstance() - .startActivityFromRecents(taskInRecents.key, opts.options)) { - mControllers.uiController.getRecentsView() - .addSideTaskLaunchCallback(opts.onEndCallback); - return; - } - } - startActivity(intent); - } else { + if (!info.user.equals(Process.myUserHandle())) { + // TODO b/376819104: support Desktop launch animations for apps in managed profiles getSystemService(LauncherApps.class).startMainActivity( intent.getComponent(), info.user, intent.getSourceBounds(), null); + return; } + // TODO(b/216683257): Use startActivityForResult for search results that require it. + if (taskInRecents != null) { + // Re launch instance from recents + ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info); + opts.options.setLaunchDisplayId( + getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); + if (ActivityManagerWrapper.getInstance() + .startActivityFromRecents(taskInRecents.key, opts.options)) { + mControllers.uiController.getRecentsView() + .addSideTaskLaunchCallback(opts.onEndCallback); + return; + } + } + ActivityOptionsWrapper opts = null; + if (areDesktopTasksVisible()) { + opts = getActivityLaunchDesktopOptions(info); + } + Bundle optionsBundle = opts == null ? null : opts.options.toBundle(); + startActivity(intent, optionsBundle); } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) .show(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt index 7b0504323e..3d57de4d2b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt @@ -73,6 +73,33 @@ class TaskbarRecentAppsController(context: Context, private val recentsModel: Re var shownTasks: List = emptyList() private set + /** + * Returns the state of the most active Desktop task represented by the given [ItemInfo]. + * + * If there are several tasks represented by the same [ItemInfo] we return the most active one, + * i.e. we return [DesktopAppState.RUNNING] over [DesktopAppState.MINIMIZED], and + * [DesktopAppState.MINIMIZED] over [DesktopAppState.NOT_RUNNING]. + */ + fun getDesktopItemState(itemInfo: ItemInfo?): RunningAppState { + val packageName = itemInfo?.getTargetPackage() ?: return RunningAppState.NOT_RUNNING + return getDesktopAppState(packageName, itemInfo.user.identifier) + } + + private fun getDesktopAppState(packageName: String, userId: Int): RunningAppState { + val tasks = desktopTask?.tasks ?: return RunningAppState.NOT_RUNNING + val appTasks = + tasks.filter { task -> + packageName == task.key.packageName && task.key.userId == userId + } + if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING } != null) { + return RunningAppState.RUNNING + } + if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED } != null) { + return RunningAppState.MINIMIZED + } + return RunningAppState.NOT_RUNNING + } + /** Get the [RunningAppState] for the given task. */ fun getRunningAppState(taskId: Int): RunningAppState { return when (taskId) { diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt index 59413d3500..066ddc0287 100644 --- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt +++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt @@ -32,6 +32,7 @@ import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDIC import com.android.launcher3.model.data.AppInfo import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.TaskItemInfo +import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.quickstep.RecentsModel import com.android.quickstep.RecentsModel.RecentTasksChangedListener import com.android.quickstep.TaskIconCache @@ -78,7 +79,9 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { private var taskListChangeId: Int = 1 private lateinit var recentAppsController: TaskbarRecentAppsController - private lateinit var userHandle: UserHandle + private lateinit var myUserHandle: UserHandle + private val USER_HANDLE_1 = UserHandle.of(1) + private val USER_HANDLE_2 = UserHandle.of(2) private var canShowRunningAndRecentAppsAtInit = true private var recentTasksChangedListener: RecentTasksChangedListener? = null @@ -86,7 +89,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { @Before fun setUp() { super.setup() - userHandle = Process.myUserHandle() + myUserHandle = Process.myUserHandle() // Set desktop mode supported whenever(mockContext.getResources()).thenReturn(mockResources) @@ -148,6 +151,84 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { verify(mockRecentsModel, times(1)).getTasks(any>>()) } + @Test + fun getDesktopItemState_nullItemInfo_returnsNotRunning() { + setInDesktopMode(true) + assertThat(recentAppsController.getDesktopItemState(/* itemInfo= */ null)) + .isEqualTo(RunningAppState.NOT_RUNNING) + } + + @Test + fun getDesktopItemState_noItemPackage_returnsNotRunning() { + setInDesktopMode(true) + assertThat(recentAppsController.getDesktopItemState(ItemInfo())) + .isEqualTo(RunningAppState.NOT_RUNNING) + } + + @Test + fun getDesktopItemState_noMatchingTasks_returnsNotRunning() { + setInDesktopMode(true) + val itemInfo = createItemInfo("package") + assertThat(recentAppsController.getDesktopItemState(itemInfo)) + .isEqualTo(RunningAppState.NOT_RUNNING) + } + + @Test + fun getDesktopItemState_matchingVisibleTask_returnsVisible() { + setInDesktopMode(true) + val visibleTask = createTask(id = 1, "visiblePackage", isVisible = true) + updateRecentTasks(runningTasks = listOf(visibleTask), recentTaskPackages = emptyList()) + val itemInfo = createItemInfo("visiblePackage") + + assertThat(recentAppsController.getDesktopItemState(itemInfo)) + .isEqualTo(RunningAppState.RUNNING) + } + + @Test + fun getDesktopItemState_matchingMinimizedTask_returnsMinimized() { + setInDesktopMode(true) + val minimizedTask = createTask(id = 1, "minimizedPackage", isVisible = false) + updateRecentTasks(runningTasks = listOf(minimizedTask), recentTaskPackages = emptyList()) + val itemInfo = createItemInfo("minimizedPackage") + + assertThat(recentAppsController.getDesktopItemState(itemInfo)) + .isEqualTo(RunningAppState.MINIMIZED) + } + + @Test + fun getDesktopItemState_matchingMinimizedAndRunningTask_returnsVisible() { + setInDesktopMode(true) + updateRecentTasks( + runningTasks = + listOf( + createTask(id = 1, "package", isVisible = false), + createTask(id = 2, "package", isVisible = true), + ), + recentTaskPackages = emptyList(), + ) + val itemInfo = createItemInfo("package") + + assertThat(recentAppsController.getDesktopItemState(itemInfo)) + .isEqualTo(RunningAppState.RUNNING) + } + + @Test + fun getDesktopItemState_noMatchingUserId_returnsNotRunning() { + setInDesktopMode(true) + updateRecentTasks( + runningTasks = + listOf( + createTask(id = 1, "package", isVisible = false, USER_HANDLE_1), + createTask(id = 2, "package", isVisible = true, USER_HANDLE_1), + ), + recentTaskPackages = emptyList(), + ) + val itemInfo = createItemInfo("package", USER_HANDLE_2) + + assertThat(recentAppsController.getDesktopItemState(itemInfo)) + .isEqualTo(RunningAppState.NOT_RUNNING) + } + @Test fun getRunningAppState_taskNotRunningOrMinimized_returnsNotRunning() { setInDesktopMode(true) @@ -814,7 +895,13 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { private fun createTestAppInfo( packageName: String = "testPackageName", className: String = "testClassName", - ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent()) + ) = + AppInfo( + ComponentName(packageName, className), + className /* title */, + myUserHandle, + Intent(), + ) private fun createRecentTasksFromPackageNames(packageNames: List): List { return packageNames.map { packageName -> @@ -833,14 +920,19 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { } } - private fun createTask(id: Int, packageName: String, isVisible: Boolean = true): Task { + private fun createTask( + id: Int, + packageName: String, + isVisible: Boolean = true, + localUserHandle: UserHandle? = null, + ): Task { return Task( Task.TaskKey( id, WINDOWING_MODE_FREEFORM, Intent().apply { `package` = packageName }, ComponentName(packageName, "TestActivity"), - userHandle.identifier, + localUserHandle?.identifier ?: myUserHandle.identifier, 0, ) ) @@ -852,6 +944,16 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() { .thenReturn(inDesktopMode) } + private fun createItemInfo( + packageName: String, + userHandle: UserHandle = myUserHandle, + ): ItemInfo { + val appInfo = AppInfo() + appInfo.intent = Intent().setComponent(ComponentName(packageName, "className")) + appInfo.user = userHandle + return WorkspaceItemInfo(appInfo) + } + private val GroupTask.packageNames: List get() = tasks.map { task -> task.key.packageName }