summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Gaurav Bhola <gauravbhola@google.com> 2025-01-07 10:48:03 -0800
committer Gaurav Bhola <gauravbhola@google.com> 2025-01-07 10:48:03 -0800
commita68d78d7e252c90354e5adf65d1727af70a2159d (patch)
treec2678e1f5f256d8b09f1ddc7900b0c82af70bef0
parent60e124646393522e0c60f6389878a7e77f4615a1 (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
-rw-r--r--libs/WindowManager/Shell/Android.bp37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt229
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt534
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt33
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