Merge "[3/n] Implement Letterbox Surfaces lifecycle" into main
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
new file mode 100644
index 0000000..406823d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Color
+import android.graphics.Rect
+import android.view.SurfaceControl
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of the letterbox surfaces.
+ */
+@WMSingleton
+class LetterboxController @Inject constructor() {
+
+ companion object {
+ /*
+ * Letterbox surfaces need to stay below the activity layer which is 0.
+ */
+ // TODO(b/378673153): Consider adding this to [TaskConstants].
+ @JvmStatic
+ private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
+ @JvmStatic
+ private val TAG = "LetterboxController"
+ }
+
+ private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxItem>()
+
+ /**
+ * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
+ */
+ fun createLetterboxSurface(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction,
+ parentLeash: SurfaceControl
+ ) {
+ letterboxMap.runOnItem(key, onMissed = { k, m ->
+ m[k] = LetterboxItem(
+ SurfaceControl.Builder()
+ .setName("ShellLetterboxSurface-$key")
+ .setHidden(true)
+ .setColorLayer()
+ .setParent(parentLeash)
+ .setCallsite("LetterboxController-createLetterboxSurface")
+ .build().apply {
+ startTransaction.setLayer(
+ this,
+ TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
+ ).setColorSpaceAgnostic(this, true)
+ // TODO(b/370940063): Implement LetterboxConfiguration
+ .setColor(this, Color.valueOf(Color.YELLOW).components)
+ }
+ )
+ })
+ }
+
+ /**
+ * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
+ */
+ fun destroyLetterboxSurface(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.fullWindowSurface?.run {
+ startTransaction.remove(this)
+ }
+ })
+ letterboxMap.remove(key)
+ }
+
+ /**
+ * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
+ */
+ fun updateLetterboxSurfaceVisibility(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction,
+ visible: Boolean = true
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.fullWindowSurface?.run {
+ startTransaction.setVisibility(this, visible)
+ }
+ })
+ }
+
+ /**
+ * Updates the bounds for the letterbox surfaces for given displayId/taskId.
+ */
+ fun updateLetterboxSurfaceBounds(
+ key: LetterboxKey,
+ startTransaction: SurfaceControl.Transaction,
+ bounds: Rect
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.fullWindowSurface?.run {
+ startTransaction.moveAndCrop(this, bounds)
+ }
+ })
+ }
+
+ /*
+ * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+ */
+ private fun MutableMap<LetterboxKey, LetterboxItem>.runOnItem(
+ key: LetterboxKey,
+ onFound: (LetterboxItem) -> Unit = { _ -> },
+ onMissed: (
+ LetterboxKey,
+ MutableMap<LetterboxKey, LetterboxItem>
+ ) -> Unit = { _, _ -> }
+ ) {
+ this[key]?.let {
+ return onFound(it)
+ }
+ return onMissed(key, this)
+ }
+
+ fun dump() {
+ ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+ }
+
+ private fun SurfaceControl.Transaction.moveAndCrop(
+ surface: SurfaceControl,
+ rect: Rect
+ ): SurfaceControl.Transaction =
+ setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(
+ surface,
+ rect.width(),
+ rect.height()
+ )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
new file mode 100644
index 0000000..98fd247
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.view.SurfaceControl
+
+// The key to use for identify the letterbox sessions.
+data class LetterboxKey(val displayId: Int, val taskId: Int)
+
+// Encapsulate the objects for the specific letterbox session.
+data class LetterboxItem(val fullWindowSurface: SurfaceControl?)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
new file mode 100644
index 0000000..67429bd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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.compatui.letterbox
+
+import android.graphics.Rect
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags.appCompatRefactoring
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import com.android.wm.shell.shared.TransitionUtil.isClosingType
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/**
+ * The [TransitionObserver] to handle Letterboxing events in Shell.
+ */
+class LetterboxTransitionObserver(
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val letterboxController: LetterboxController
+) : Transitions.TransitionObserver {
+
+ companion object {
+ @JvmStatic
+ private val TAG = "LetterboxTransitionObserver"
+ }
+
+ init {
+ if (appCompatRefactoring()) {
+ ProtoLog.v(
+ WM_SHELL_APP_COMPAT,
+ "%s: %s",
+ TAG,
+ "Initializing LetterboxTransitionObserver"
+ )
+ shellInit.addInitCallback({
+ transitions.registerObserver(this)
+ }, this)
+ }
+ }
+
+ override fun onTransitionReady(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
+ ) {
+ // We recognise the operation to execute and delegate to the LetterboxController
+ // the related operation.
+ // TODO(b/377875151): Identify Desktop Windowing Transactions.
+ // TODO(b/377857898): Handling multiple surfaces
+ // TODO(b/371500295): Handle input events detection.
+ for (change in info.changes) {
+ change.taskInfo?.let { ti ->
+ val key = LetterboxKey(ti.displayId, ti.taskId)
+ if (isClosingType(change.mode)) {
+ letterboxController.destroyLetterboxSurface(
+ key,
+ startTransaction
+ )
+ } else {
+ val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
+ if (isTopActivityLetterboxed) {
+ letterboxController.createLetterboxSurface(
+ key,
+ startTransaction,
+ change.leash
+ )
+ letterboxController.updateLetterboxSurfaceBounds(
+ key,
+ startTransaction,
+ Rect(
+ change.endRelOffset.x,
+ change.endRelOffset.y,
+ change.endAbsBounds.width(),
+ change.endAbsBounds.height()
+ )
+ )
+ }
+ letterboxController.updateLetterboxSurfaceVisibility(
+ key,
+ startTransaction,
+ isTopActivityLetterboxed
+ )
+ }
+ letterboxController.dump()
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 601cf70..307e12e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -20,6 +20,7 @@
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.content.Context;
@@ -62,6 +63,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.compatui.letterbox.LetterboxController;
+import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -1229,8 +1232,23 @@
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
+ @NonNull LetterboxTransitionObserver letterboxTransitionObserver,
Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
return new Object();
}
+
+ //
+ // App Compat
+ //
+
+ @WMSingleton
+ @Provides
+ static LetterboxTransitionObserver provideLetterboxTransitionObserver(
+ @NonNull ShellInit shellInit,
+ @NonNull Transitions transitions,
+ @NonNull LetterboxController letterboxController
+ ) {
+ return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
+ }
}