/* * 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.launcher3.desktop import android.app.IApplicationThread import android.os.IBinder import android.os.RemoteException import android.util.Log import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.IRemoteTransitionFinishedCallback import android.window.RemoteTransition import android.window.RemoteTransitionStub import android.window.TransitionInfo import com.android.launcher3.statehandlers.DepthController import com.android.launcher3.statemanager.StateManager import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.quickstep.SystemUiProxy import com.android.quickstep.TaskViewUtils import com.android.quickstep.util.DesksUtils.Companion.areMultiDesksFlagsEnabled import com.android.quickstep.views.DesktopTaskView import com.android.quickstep.views.TaskContainer import com.android.quickstep.views.TaskView import com.android.window.flags.Flags import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource import java.util.function.Consumer /** Manage recents related operations with desktop tasks */ class DesktopRecentsTransitionController( private val stateManager: StateManager<*, *>, private val systemUiProxy: SystemUiProxy, private val appThread: IApplicationThread, private val depthController: DepthController?, ) { /** Launch desktop tasks from recents view */ fun launchDesktopFromRecents( desktopTaskView: DesktopTaskView, animated: Boolean, callback: Consumer? = null, ) { val animRunner = RemoteDesktopLaunchTransitionRunner( desktopTaskView, animated, stateManager, depthController, callback, ) val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop") if (areMultiDesksFlagsEnabled()) { systemUiProxy.activateDesk(desktopTaskView.deskId, transition) } else { systemUiProxy.showDesktopApps(desktopTaskView.displayId, transition) } } /** Launch desktop tasks from recents view */ fun moveToDesktop( taskContainer: TaskContainer, transitionSource: DesktopModeTransitionSource, successCallback: Runnable, ) { systemUiProxy.moveToDesktop( taskContainer.task.key.id, transitionSource, /* transition = */ null, successCallback, ) } /** Move task to external display from recents view */ fun moveToExternalDisplay(taskId: Int) { systemUiProxy.moveToExternalDisplay(taskId) } private class RemoteDesktopLaunchTransitionRunner( private val taskView: TaskView, private val animated: Boolean, private val stateManager: StateManager<*, *>, private val depthController: DepthController?, private val successCallback: Consumer?, ) : RemoteTransitionStub() { override fun startAnimation( token: IBinder, info: TransitionInfo, t: SurfaceControl.Transaction, finishCallback: IRemoteTransitionFinishedCallback, ) { val errorHandlingFinishCallback = Runnable { try { finishCallback.onTransitionFinished(null /* wct */, null /* sct */) } catch (e: RemoteException) { Log.e(TAG, "Failed to call finish callback for desktop recents animation", e) } } if (Flags.enableDesktopWindowingPersistence()) { handleAnimationAfterReboot(info) } MAIN_EXECUTOR.execute { val animator = TaskViewUtils.composeRecentsDesktopLaunchAnimator( taskView, stateManager, depthController, info, t, ) { errorHandlingFinishCallback.run() successCallback?.accept(true) } if (!animated) { animator.setDuration(0) } animator.start() } } /** * Upon reboot the start bounds of a task is set to fullscreen with the recents transition. * Check this case and set the start bounds to the end bounds so that the window doesn't * jump from start bounds to end bounds during the animation. Tasks in desktop cannot * normally have top bound as 0 due to status bar so this is a good indicator to identify * reboot case. */ private fun handleAnimationAfterReboot(info: TransitionInfo) { info.changes.forEach { change -> if ( change.mode == TRANSIT_TO_FRONT && change.taskInfo?.isFreeform == true && change.startAbsBounds.top == 0 && change.startAbsBounds.left == 0 ) { change.setStartAbsBounds(change.endAbsBounds) } } } } companion object { const val TAG = "DesktopRecentsTransitionController" } }