diff options
author | 2025-01-07 10:48:03 -0800 | |
---|---|---|
committer | 2025-01-07 10:48:03 -0800 | |
commit | a68d78d7e252c90354e5adf65d1727af70a2159d (patch) | |
tree | c2678e1f5f256d8b09f1ddc7900b0c82af70bef0 | |
parent | 60e124646393522e0c60f6389878a7e77f4615a1 (diff) |
Add autotaskstackcontroller.
- Provides opinionated APIs to implement task stack based windowing.
- Uses shell transitions to enforce a custom z-layer for the task stacks.
Flag: com.android.systemui.car.auto_task_stack_windowing
Test: m
Bug: 384082238
Change-Id: I8f4ee2c71a8d574694ef8eb0ee5ac54e474f0a6d
7 files changed, 912 insertions, 18 deletions
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 4c75ea4777da..957d1b835ec2 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -26,8 +26,8 @@ package { java_library { name: "wm_shell_protolog-groups", srcs: [ - "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java", ":protolog-common-src", + "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java", ], } @@ -61,8 +61,8 @@ java_genrule { name: "wm_shell_protolog_src", srcs: [ ":protolog-impl", - ":wm_shell_protolog-groups", ":wm_shell-sources", + ":wm_shell_protolog-groups", ], tools: ["protologtool"], cmd: "$(location protologtool) transform-protolog-calls " + @@ -80,8 +80,8 @@ java_genrule { java_genrule { name: "generate-wm_shell_protolog.json", srcs: [ - ":wm_shell_protolog-groups", ":wm_shell-sources", + ":wm_shell_protolog-groups", ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + @@ -97,8 +97,8 @@ java_genrule { java_genrule { name: "gen-wmshell.protolog.pb", srcs: [ - ":wm_shell_protolog-groups", ":wm_shell-sources", + ":wm_shell_protolog-groups", ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + @@ -159,38 +159,39 @@ java_library { android_library { name: "WindowManager-Shell", srcs: [ - "src/com/android/wm/shell/EventLogTags.logtags", ":wm_shell_protolog_src", // TODO(b/168581922) protologtool do not support kotlin(*.kt) - ":wm_shell-sources-kt", + "src/com/android/wm/shell/EventLogTags.logtags", ":wm_shell-aidls", ":wm_shell-shared-aidls", + ":wm_shell-sources-kt", ], resource_dirs: [ "res", ], static_libs: [ + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", + "//frameworks/libs/systemui:iconloader_base", + "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib", + "PlatformAnimationLib", + "WindowManager-Shell-lite-proto", + "WindowManager-Shell-proto", + "WindowManager-Shell-shared", + "androidx-constraintlayout_constraintlayout", "androidx.appcompat_appcompat", - "androidx.core_core-ktx", "androidx.arch.core_core-runtime", - "androidx.datastore_datastore", "androidx.compose.material3_material3", - "androidx-constraintlayout_constraintlayout", + "androidx.core_core-ktx", + "androidx.datastore_datastore", "androidx.dynamicanimation_dynamicanimation", "androidx.recyclerview_recyclerview", - "kotlinx-coroutines-android", - "kotlinx-coroutines-core", - "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", - "//frameworks/libs/systemui:iconloader_base", "com_android_launcher3_flags_lib", "com_android_wm_shell_flags_lib", - "PlatformAnimationLib", - "WindowManager-Shell-proto", - "WindowManager-Shell-lite-proto", - "WindowManager-Shell-shared", - "perfetto_trace_java_protos", "dagger2", "jsr330", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", + "perfetto_trace_java_protos", ], libs: [ // Soong fails to automatically add this dependency because all the diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 9f01316d5b5c..b098620fde2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -230,6 +230,11 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { return mDisplayAreasInfo.get(displayId); } + @Nullable + public SurfaceControl getDisplayAreaLeash(int displayId) { + return mLeashes.get(displayId); + } + /** * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by * {@link DisplayAreaInfo#displayId}. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java new file mode 100644 index 000000000000..fc51c754e06b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 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.wm.shell.automotive; + +import com.android.wm.shell.dagger.WMSingleton; + +import dagger.Binds; +import dagger.Module; + + +@Module +public abstract class AutoShellModule { + @WMSingleton + @Binds + abstract AutoTaskStackController provideTaskStackController(AutoTaskStackControllerImpl impl); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt new file mode 100644 index 000000000000..caacdd355996 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 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.wm.shell.automotive + +import android.app.ActivityManager +import android.graphics.Rect +import android.view.SurfaceControl + +/** + * Represents an auto task stack, which is always in multi-window mode. + * + * @property id The ID of the task stack. + * @property displayId The ID of the display the task stack is on. + * @property leash The surface control leash of the task stack. + */ +interface AutoTaskStack { + val id: Int + val displayId: Int + var leash: SurfaceControl +} + +/** + * Data class representing the state of an auto task stack. + * + * @property bounds The bounds of the task stack. + * @property childrenTasksVisible Whether the child tasks of the stack are visible. + * @property layer The layer of the task stack. + */ +data class AutoTaskStackState( + val bounds: Rect = Rect(), + val childrenTasksVisible: Boolean, + val layer: Int +) + +/** + * Data class representing a root task stack. + * + * @property id The ID of the root task stack + * @property displayId The ID of the display the root task stack is on. + * @property leash The surface control leash of the root task stack. + * @property rootTaskInfo The running task info of the root task. + */ +data class RootTaskStack( + override val id: Int, + override val displayId: Int, + override var leash: SurfaceControl, + var rootTaskInfo: ActivityManager.RunningTaskInfo +) : AutoTaskStack diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt new file mode 100644 index 000000000000..15fedac62af3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2024 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.wm.shell.automotive + +import android.app.PendingIntent +import android.content.Intent +import android.os.Bundle +import android.os.IBinder +import android.view.SurfaceControl +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback + +/** + * Delegate interface for handling auto task stack transitions. + */ +interface AutoTaskStackTransitionHandlerDelegate { + /** + * Handles a transition request. + * + * @param transition The transition identifier. + * @param request The transition request information. + * @return An [AutoTaskStackTransaction] to be applied for the transition, or null if the + * animation is not handled by this delegate. + */ + fun handleRequest( + transition: IBinder, request: TransitionRequestInfo + ): AutoTaskStackTransaction? + + /** + * See [Transitions.TransitionHandler.startAnimation] for more details. + * + * @param changedTaskStacks Contains the states of the task stacks that were changed as a + * result of this transition. The key is the [AutoTaskStack.id] and the value is the + * corresponding [AutoTaskStackState]. + */ + fun startAnimation( + transition: IBinder, + changedTaskStacks: Map<Int, AutoTaskStackState>, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction, + finishCallback: TransitionFinishCallback + ): Boolean + + /** + * See [Transitions.TransitionHandler.onTransitionConsumed] for more details. + * + * @param requestedTaskStacks contains the states of the task stacks that were requested in + * the transition. The key is the [AutoTaskStack.id] and the value is the corresponding + * [AutoTaskStackState]. + */ + fun onTransitionConsumed( + transition: IBinder, + requestedTaskStacks: Map<Int, AutoTaskStackState>, + aborted: Boolean, finishTransaction: SurfaceControl.Transaction? + ) + + /** + * See [Transitions.TransitionHandler.mergeAnimation] for more details. + * + * @param changedTaskStacks Contains the states of the task stacks that were changed as a + * result of this transition. The key is the [AutoTaskStack.id] and the value is the + * corresponding [AutoTaskStackState]. + */ + fun mergeAnimation( + transition: IBinder, + changedTaskStacks: Map<Int, AutoTaskStackState>, + info: TransitionInfo, + surfaceTransaction: SurfaceControl.Transaction, + mergeTarget: IBinder, + finishCallback: TransitionFinishCallback + ) +} + + +/** + * Controller for managing auto task stacks. + */ +interface AutoTaskStackController { + + var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate? + set + + /** + * Map of task stack IDs to their states. + * + * This gets updated right before [AutoTaskStackTransitionHandlerDelegate.startAnimation] or + * [AutoTaskStackTransitionHandlerDelegate.onTransitionConsumed] is called. + */ + val taskStackStateMap: Map<Int, AutoTaskStackState> + get + + /** + * Creates a new multi-window root task. + * + * A root task stack is placed in the default TDA of the specified display by default. + * Once the root task is removed, the [AutoTaskStackController] no longer holds a reference to + * it. + * + * @param displayId The ID of the display to create the root task stack on. + * @param listener The listener for root task stack events. + */ + @ShellMainThread + fun createRootTaskStack(displayId: Int, listener: RootTaskStackListener) + + + /** + * Sets the default root task stack (launch root) on a display. Calling it again with a + * different [rootTaskStackId] will simply replace the default root task stack on the display. + * + * Note: This is helpful for passively routing tasks to a specified container. If a display + * doesn't have a default root task stack set, all tasks will open in fullscreen and cover + * the entire default TDA by default. + * + * @param displayId The ID of the display. + * @param rootTaskStackId The ID of the root task stack, or null to clear the default. + */ + @ShellMainThread + fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) + + /** + * Starts a transaction with the specified [transaction]. + * Returns the transition identifier. + */ + @ShellMainThread + fun startTransition(transaction: AutoTaskStackTransaction): IBinder? +} + +internal sealed class TaskStackOperation { + data class ReparentTask( + val taskId: Int, + val parentTaskStackId: Int, + val onTop: Boolean + ) : TaskStackOperation() + + data class SendPendingIntent( + val sender: PendingIntent, + val intent: Intent, + val options: Bundle? + ) : TaskStackOperation() + + data class SetTaskStackState( + val taskStackId: Int, + val state: AutoTaskStackState + ) : TaskStackOperation() +} + +data class AutoTaskStackTransaction internal constructor( + internal val operations: MutableList<TaskStackOperation> = mutableListOf() +) { + constructor() : this( + mutableListOf() + ) + + /** See [WindowContainerTransaction.reparent] for more details. */ + fun reparentTask( + taskId: Int, + parentTaskStackId: Int, + onTop: Boolean + ): AutoTaskStackTransaction { + operations.add(TaskStackOperation.ReparentTask(taskId, parentTaskStackId, onTop)) + return this + } + + /** See [WindowContainerTransaction.sendPendingIntent] for more details. */ + fun sendPendingIntent( + sender: PendingIntent, + intent: Intent, + options: Bundle? + ): AutoTaskStackTransaction { + operations.add(TaskStackOperation.SendPendingIntent(sender, intent, options)) + return this + } + + /** + * Adds a set task stack state operation to the transaction. + * + * If an operation with the same task stack ID already exists, it is replaced with the new one. + * + * @param taskStackId The ID of the task stack. + * @param state The new state of the task stack. + * @return The transaction with the added operation. + */ + fun setTaskStackState(taskStackId: Int, state: AutoTaskStackState): AutoTaskStackTransaction { + val existingOperation = operations.find { + it is TaskStackOperation.SetTaskStackState && it.taskStackId == taskStackId + } + if (existingOperation != null) { + val index = operations.indexOf(existingOperation) + operations[index] = TaskStackOperation.SetTaskStackState(taskStackId, state) + } else { + operations.add(TaskStackOperation.SetTaskStackState(taskStackId, state)) + } + return this + } + + /** + * Returns a map of task stack IDs to their states from the set task stack state operations. + * + * @return The map of task stack IDs to states. + */ + fun getTaskStackStates(): Map<Int, AutoTaskStackState> { + val states = mutableMapOf<Int, AutoTaskStackState>() + operations.forEach { operation -> + if (operation is TaskStackOperation.SetTaskStackState) { + states[operation.taskStackId] = operation.state + } + } + return states + } +} + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt new file mode 100644 index 000000000000..f8f284238a98 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2024 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.wm.shell.automotive + +import android.app.ActivityManager +import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT +import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW +import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.os.IBinder +import android.util.Log +import android.util.Slog +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.view.WindowManager +import android.view.WindowManager.TRANSIT_CHANGE +import android.window.TransitionInfo +import android.window.TransitionRequestInfo +import android.window.WindowContainerTransaction +import com.android.systemui.car.Flags.autoTaskStackWindowing +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTaskOrganizer +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.dagger.WMSingleton +import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback +import javax.inject.Inject + +const val TAG = "AutoTaskStackController" + +@WMSingleton +class AutoTaskStackControllerImpl @Inject constructor( + val taskOrganizer: ShellTaskOrganizer, + @ShellMainThread private val shellMainThread: ShellExecutor, + val transitions: Transitions, + val shellInit: ShellInit, + val rootTdaOrganizer: RootTaskDisplayAreaOrganizer +) : AutoTaskStackController, Transitions.TransitionHandler { + override var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate? = null + override val taskStackStateMap = mutableMapOf<Int, AutoTaskStackState>() + + private val DBG = Log.isLoggable(TAG, Log.DEBUG) + private val taskStackMap = mutableMapOf<Int, AutoTaskStack>() + private val pendingTransitions = ArrayList<PendingTransition>() + private val mTaskStackStateTranslator = TaskStackStateTranslator() + private val appTasksMap = mutableMapOf<Int, ActivityManager.RunningTaskInfo>() + private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>() + + init { + if (!autoTaskStackWindowing()) { + throw IllegalStateException("Failed to initialize" + + "AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.") + } else { + shellInit.addInitCallback(this::onInit, this); + } + } + + fun onInit() { + transitions.addHandler(this) + } + + /** Translates the [AutoTaskStackState] to relevant WM and surface transactions. */ + inner class TaskStackStateTranslator { + // TODO(b/384946072): Move to an interface with 2 implementations, one for root task and + // other for TDA + fun applyVisibilityAndBounds( + wct: WindowContainerTransaction, + taskStack: AutoTaskStack, + state: AutoTaskStackState + ) { + if (taskStack !is RootTaskStack) { + Slog.e(TAG, "Unsupported task stack, unable to convertToWct") + return + } + wct.setBounds(taskStack.rootTaskInfo.token, state.bounds) + wct.reorder(taskStack.rootTaskInfo.token, /* onTop= */ state.childrenTasksVisible) + } + + fun reorderLeash( + taskStack: AutoTaskStack, + state: AutoTaskStackState, + transaction: Transaction + ) { + if (taskStack !is RootTaskStack) { + Slog.e(TAG, "Unsupported task stack, unable to reorder leash") + return + } + Slog.d(TAG, "Setting the layer ${state.layer}") + transaction.setLayer(taskStack.leash, state.layer) + } + + fun restoreLeash(taskStack: AutoTaskStack, transaction: Transaction) { + if (taskStack !is RootTaskStack) { + Slog.e(TAG, "Unsupported task stack, unable to restore leash") + return + } + + val rootTdaInfo = rootTdaOrganizer.getDisplayAreaInfo(taskStack.displayId) + if (rootTdaInfo == null || + rootTdaInfo.featureId != taskStack.rootTaskInfo.displayAreaFeatureId + ) { + Slog.e(TAG, "Cannot find the rootTDA for the root task stack ${taskStack.id}") + return + } + if (DBG) { + Slog.d(TAG, "Reparenting ${taskStack.id} leash to DA ${rootTdaInfo.featureId}") + } + transaction.reparent( + taskStack.leash, + rootTdaOrganizer.getDisplayAreaLeash(taskStack.displayId) + ) + } + } + + inner class RootTaskStackListenerAdapter( + val rootTaskStackListener: RootTaskStackListener, + ) : ShellTaskOrganizer.TaskListener { + private var rootTaskStack: RootTaskStack? = null + + // TODO(b/384948029): Notify car service for all the children tasks' events + override fun onTaskAppeared( + taskInfo: ActivityManager.RunningTaskInfo?, + leash: SurfaceControl? + ) { + if (taskInfo == null) { + throw IllegalArgumentException("taskInfo can't be null in onTaskAppeared") + } + if (leash == null) { + throw IllegalArgumentException("leash can't be null in onTaskAppeared") + } + if (DBG) Slog.d(TAG, "onTaskAppeared = ${taskInfo.taskId}") + + if (rootTaskStack == null) { + val rootTask = + RootTaskStack(taskInfo.taskId, taskInfo.displayId, leash, taskInfo) + taskStackMap[rootTask.id] = rootTask + + rootTaskStack = rootTask; + rootTaskStackListener.onRootTaskStackCreated(rootTask); + return + } + appTasksMap[taskInfo.taskId] = taskInfo + rootTaskStackListener.onTaskAppeared(taskInfo, leash) + } + + override fun onTaskInfoChanged(taskInfo: ActivityManager.RunningTaskInfo?) { + if (taskInfo == null) { + throw IllegalArgumentException("taskInfo can't be null in onTaskInfoChanged") + } + if (DBG) Slog.d(TAG, "onTaskInfoChanged = ${taskInfo.taskId}") + var previousRootTaskStackInfo = rootTaskStack ?: run { + Slog.e(TAG, "Received onTaskInfoChanged, when root task stack is null") + return@onTaskInfoChanged + } + rootTaskStack?.let { + if (taskInfo.taskId == previousRootTaskStackInfo.id) { + previousRootTaskStackInfo = previousRootTaskStackInfo.copy(rootTaskInfo = taskInfo) + taskStackMap[previousRootTaskStackInfo.id] = previousRootTaskStackInfo + rootTaskStack = previousRootTaskStackInfo; + rootTaskStackListener.onRootTaskStackInfoChanged(it) + return + } + } + + appTasksMap[taskInfo.taskId] = taskInfo + rootTaskStackListener.onTaskInfoChanged(taskInfo) + } + + override fun onTaskVanished(taskInfo: ActivityManager.RunningTaskInfo?) { + if (taskInfo == null) { + throw IllegalArgumentException("taskInfo can't be null in onTaskVanished") + } + if (DBG) Slog.d(TAG, "onTaskVanished = ${taskInfo.taskId}") + var rootTask = rootTaskStack ?: run { + Slog.e(TAG, "Received onTaskVanished, when root task stack is null") + return@onTaskVanished + } + if (taskInfo.taskId == rootTask.id) { + rootTask = rootTask.copy(rootTaskInfo = taskInfo) + rootTaskStack = rootTask + rootTaskStackListener.onRootTaskStackDestroyed(rootTask) + taskStackMap.remove(rootTask.id) + taskStackStateMap.remove(rootTask.id) + rootTaskStack = null + return + } + appTasksMap.remove(taskInfo.taskId) + rootTaskStackListener.onTaskVanished(taskInfo) + } + + override fun onBackPressedOnTaskRoot(taskInfo: ActivityManager.RunningTaskInfo?) { + if (taskInfo == null) { + throw IllegalArgumentException("taskInfo can't be null in onBackPressedOnTaskRoot") + } + super.onBackPressedOnTaskRoot(taskInfo) + rootTaskStackListener.onBackPressedOnTaskRoot(taskInfo) + } + } + + override fun createRootTaskStack( + displayId: Int, + listener: RootTaskStackListener + ) { + if (!autoTaskStackWindowing()) { + Slog.e( + TAG, "Failed to create root task stack as the " + + "auto_task_stack_windowing TS flag is disabled." + ) + return + } + taskOrganizer.createRootTask( + displayId, + WINDOWING_MODE_MULTI_WINDOW, + RootTaskStackListenerAdapter(listener), + /* removeWithTaskOrganizer= */ true + ) + } + + override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) { + if (!autoTaskStackWindowing()) { + Slog.e( + TAG, "Failed to set default root task stack as the " + + "auto_task_stack_windowing TS flag is disabled." + ) + return + } + var wct = WindowContainerTransaction() + + // Clear the default root task stack if already set + defaultRootTaskPerDisplay[displayId]?.let { existingDefaultRootTaskStackId -> + (taskStackMap[existingDefaultRootTaskStackId] as? RootTaskStack)?.let { rootTaskStack -> + wct.setLaunchRoot(rootTaskStack.rootTaskInfo.token, null, null) + } + } + + if (rootTaskStackId != null) { + var taskStack = + taskStackMap[rootTaskStackId] ?: run { return@setDefaultRootTaskStackOnDisplay } + if (DBG) Slog.d(TAG, "setting launch root for = ${taskStack.id}") + if (taskStack !is RootTaskStack) { + throw IllegalArgumentException( + "Cannot set a non root task stack as default root task " + + "stack" + ) + } + wct.setLaunchRoot( + taskStack.rootTaskInfo.token, + intArrayOf(WINDOWING_MODE_UNDEFINED), + intArrayOf( + ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_RECENTS, + + // TODO(b/386242708): Figure out if this flag will ever be used for automotive + // assistant. Based on output, remove it from here and fix the + // AssistantStackTests accordingly. + ACTIVITY_TYPE_ASSISTANT + ) + ) + } + + taskOrganizer.applyTransaction(wct) + } + + override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? { + if (!autoTaskStackWindowing()) { + Slog.e( + TAG, "Failed to start transaction as the " + + "auto_task_stack_windowing TS flag is disabled." + ) + return null + } + if (transaction.operations.isEmpty()) { + Slog.e(TAG, "Operations empty, no transaction started") + return null + } + if (DBG) Slog.d(TAG, "startTransaction ${transaction.operations}") + + var wct = WindowContainerTransaction() + convertToWct(transaction, wct) + var pending = PendingTransition( + TRANSIT_CHANGE, + wct, + transaction, + ) + return startTransitionNow(pending) + } + + override fun handleRequest( + transition: IBinder, + request: TransitionRequestInfo + ): WindowContainerTransaction? { + if (DBG) { + Slog.d(TAG, "handle request, id=${request.debugId}, type=${request.type}, " + + "triggertask = ${request.triggerTask ?: "null"}") + } + val ast = autoTransitionHandlerDelegate?.handleRequest(transition, request) + ?: run { return@handleRequest null } + + if (ast.operations.isEmpty()) { + return null + } + var wct = WindowContainerTransaction() + convertToWct(ast, wct) + + pendingTransitions.add( + PendingTransition(request.type, wct, ast).apply { isClaimed = transition } + ) + return wct + } + + fun updateTaskStackStates(taskStatStates: Map<Int, AutoTaskStackState>) { + taskStackStateMap.putAll(taskStatStates) + } + + override fun startAnimation( + transition: IBinder, + info: TransitionInfo, + startTransaction: Transaction, + finishTransaction: Transaction, + finishCallback: TransitionFinishCallback + ): Boolean { + if (DBG) Slog.d(TAG, " startAnimation, id=${info.debugId} = changes=" + info.changes) + val pending: PendingTransition? = findPending(transition) + if (pending != null) { + pendingTransitions.remove(pending) + updateTaskStackStates(pending.transaction.getTaskStackStates()) + } + + reorderLeashes(startTransaction) + reorderLeashes(finishTransaction) + + for (chg in info.changes) { + // TODO(b/384946072): handle the da stack similarly. The below implementation only + // handles the root task stack + + val taskInfo = chg.taskInfo ?: continue + val taskStack = taskStackMap[taskInfo.taskId] ?: continue + + // Restore the leashes for the task stacks to ensure correct z-order competition + if (taskStackMap.containsKey(taskInfo.taskId)) { + mTaskStackStateTranslator.restoreLeash( + taskStack, + startTransaction + ) + if (TransitionUtil.isOpeningMode(chg.mode)) { + // Clients can still manipulate the alpha, but this ensures that the default + // behavior is natural + startTransaction.setAlpha(chg.leash, 1f) + } + continue + } + } + + val isPlayedByDelegate = autoTransitionHandlerDelegate?.startAnimation( + transition, + pending?.transaction?.getTaskStackStates() ?: mapOf(), + info, + startTransaction, + finishTransaction, + { + shellMainThread.execute { + finishCallback.onTransitionFinished(it) + startNextTransition() + } + } + ) ?: false + + if (isPlayedByDelegate) { + if (DBG) Slog.d(TAG, "${info.debugId} played"); + return true; + } + + // If for an animation which is not played by the delegate, contains a change in a known + // task stack, it should be leveraged to correct the leashes. So, handle the animation in + // this case. + if (info.changes.any { taskStackMap.containsKey(it.taskInfo?.taskId) }) { + startTransaction.apply() + finishCallback.onTransitionFinished(null) + startNextTransition() + if (DBG) Slog.d(TAG, "${info.debugId} played"); + return true + } + return false; + } + + fun convertToWct(ast: AutoTaskStackTransaction, wct: WindowContainerTransaction) { + ast.operations.forEach { operation -> + when (operation) { + is TaskStackOperation.ReparentTask -> { + val appTask = appTasksMap[operation.taskId] + + if (appTask == null) { + Slog.e( + TAG, "task with id=$operation.taskId not found, failed to " + + "reparent." + ) + return@forEach + } + if (!taskStackMap.containsKey(operation.parentTaskStackId)) { + Slog.e( + TAG, "task stack with id=${operation.parentTaskStackId} not " + + "found, failed to reparent" + ) + return@forEach + } + // TODO(b/384946072): Handle a display area stack as well + wct.reparent( + appTask.token, + (taskStackMap[operation.parentTaskStackId] as RootTaskStack) + .rootTaskInfo.token, + operation.onTop + ) + } + + is TaskStackOperation.SendPendingIntent -> wct.sendPendingIntent( + operation.sender, + operation.intent, + operation.options + ) + + is TaskStackOperation.SetTaskStackState -> { + taskStackMap[operation.taskStackId]?.let { taskStack -> + mTaskStackStateTranslator.applyVisibilityAndBounds( + wct, + taskStack, + operation.state + ) + } + ?: Slog.w(TAG, "AutoTaskStack with id ${operation.taskStackId} " + + "not found.") + } + } + } + } + + override fun mergeAnimation( + transition: IBinder, + info: TransitionInfo, + surfaceTransaction: Transaction, + mergeTarget: IBinder, + finishCallback: TransitionFinishCallback + ) { + val pending: PendingTransition? = findPending(transition) + + autoTransitionHandlerDelegate?.mergeAnimation( + transition, + pending?.transaction?.getTaskStackStates() ?: mapOf(), + info, + surfaceTransaction, + mergeTarget, + /* finishCallback = */ { + shellMainThread.execute { + finishCallback.onTransitionFinished(it) + } + } + ) + } + + override fun onTransitionConsumed( + transition: IBinder, + aborted: Boolean, + finishTransaction: Transaction? + ) { + val pending: PendingTransition? = findPending(transition) + if (pending != null) { + pendingTransitions.remove(pending) + updateTaskStackStates(pending.transaction.getTaskStackStates()) + // Still update the surface order because this means wm didn't lead to any change + if (finishTransaction != null) { + reorderLeashes(finishTransaction) + } + } + autoTransitionHandlerDelegate?.onTransitionConsumed( + transition, + pending?.transaction?.getTaskStackStates() ?: mapOf(), + aborted, + finishTransaction + ) + } + + private fun reorderLeashes(transaction: SurfaceControl.Transaction) { + taskStackStateMap.forEach { (taskId, taskStackState) -> + taskStackMap[taskId]?.let { taskStack -> + mTaskStackStateTranslator.reorderLeash(taskStack, taskStackState, transaction) + } ?: Slog.w(TAG, "Warning: AutoTaskStack with id $taskId not found.") + } + } + + private fun findPending(claimed: IBinder) = pendingTransitions.find { it.isClaimed == claimed } + + private fun startTransitionNow(pending: PendingTransition): IBinder { + val claimedTransition = transitions.startTransition(pending.mType, pending.wct, this) + pending.isClaimed = claimedTransition + pendingTransitions.add(pending) + return claimedTransition + } + + fun startNextTransition() { + if (pendingTransitions.isEmpty()) return + val pending: PendingTransition = pendingTransitions[0] + if (pending.isClaimed != null) { + // Wait for this to start animating. + return + } + pending.isClaimed = transitions.startTransition(pending.mType, pending.wct, this) + } + + internal class PendingTransition( + @field:WindowManager.TransitionType @param:WindowManager.TransitionType val mType: Int, + val wct: WindowContainerTransaction, + val transaction: AutoTaskStackTransaction, + ) { + var isClaimed: IBinder? = null + } + +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt new file mode 100644 index 000000000000..9d121b144492 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 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.wm.shell.automotive + +import com.android.wm.shell.ShellTaskOrganizer + +/** + * A [TaskListener] which simplifies the interface when used for + * [ShellTaskOrganizer.createRootTask]. + * + * [onRootTaskStackCreated], [onRootTaskStackInfoChanged], [onRootTaskStackDestroyed] will be called + * for the underlying root task. + * The [onTaskAppeared], [onTaskInfoChanged], [onTaskVanished] are called for the children tasks. + */ +interface RootTaskStackListener : ShellTaskOrganizer.TaskListener { + fun onRootTaskStackCreated(rootTaskStack: RootTaskStack) + fun onRootTaskStackInfoChanged(rootTaskStack: RootTaskStack) + fun onRootTaskStackDestroyed(rootTaskStack: RootTaskStack) +}
\ No newline at end of file |