Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0cb97b51 authored by Massimo Carli's avatar Massimo Carli
Browse files

[7/n] Create LetterboxController abstraction

Creates the LetterboxController abstraction to allow different
operations on LetterboxObserver callbacks.

Flag: com.android.window.flags.app_compat_refactoring
Bug: 377857898
Test: atest WMShellUnitTests:LetterboxTransitionObserverTest

Change-Id: I8878901fb49c35fd6703956ffebdb8359d768707
parent ffeea751
Loading
Loading
Loading
Loading
+17 −99
Original line number Diff line number Diff line
@@ -18,133 +18,51 @@ package com.android.wm.shell.compatui.letterbox

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
import android.view.SurfaceControl.Transaction

/**
 * Component responsible for handling the lifecycle of the letterbox surfaces.
 * Abstracts the component responsible to handle a single or multiple letterbox surfaces for a
 * specific [Change].
 */
@WMSingleton
class LetterboxController @Inject constructor(
    private val letterboxConfiguration: LetterboxConfiguration
) {

    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>()
interface LetterboxController {

    /**
     * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
     */
    fun createLetterboxSurface(
        key: LetterboxKey,
        startTransaction: SurfaceControl.Transaction,
        transaction: 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)
                            .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
                    }
    )
        })
    }

    /**
     * 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)
    }
        transaction: Transaction
    )

    /**
     * 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)
            }
        })
    }
        transaction: Transaction,
        visible: Boolean
    )

    /**
     * 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)
            }
        })
    }
        transaction: Transaction,
        taskBounds: Rect
    )

    /*
     * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
    /**
     * Utility method to dump the current state.
     */
    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()
            )
    fun dump()
}
+1 −6
Original line number Diff line number Diff line
@@ -16,10 +16,5 @@

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)
 No newline at end of file

// Encapsulate the objects for the specific letterbox session.
data class LetterboxItem(val fullWindowSurface: SurfaceControl?)
 No newline at end of file
+32 −30
Original line number Diff line number Diff line
@@ -43,12 +43,7 @@ class LetterboxTransitionObserver(

    init {
        if (appCompatRefactoring()) {
            ProtoLog.v(
                WM_SHELL_APP_COMPAT,
                "%s: %s",
                TAG,
                "Initializing LetterboxTransitionObserver"
            )
            logV("Initializing LetterboxTransitionObserver")
            shellInit.addInitCallback({
                transitions.registerObserver(this)
            }, this)
@@ -69,38 +64,45 @@ class LetterboxTransitionObserver(
        for (change in info.changes) {
            change.taskInfo?.let { ti ->
                val key = LetterboxKey(ti.displayId, ti.taskId)
                val taskBounds = Rect(
                    change.endRelOffset.x,
                    change.endRelOffset.y,
                    change.endAbsBounds.width(),
                    change.endAbsBounds.height()
                )
                with(letterboxController) {
                    if (isClosingType(change.mode)) {
                    letterboxController.destroyLetterboxSurface(
                        destroyLetterboxSurface(
                            key,
                            startTransaction
                        )
                    } else {
                        val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
                        if (isTopActivityLetterboxed) {
                        letterboxController.createLetterboxSurface(
                            createLetterboxSurface(
                                key,
                                startTransaction,
                                change.leash
                            )
                        letterboxController.updateLetterboxSurfaceBounds(
                            updateLetterboxSurfaceBounds(
                                key,
                                startTransaction,
                            Rect(
                                change.endRelOffset.x,
                                change.endRelOffset.y,
                                change.endAbsBounds.width(),
                                change.endAbsBounds.height()
                            )
                                taskBounds
                            )
                        }
                    letterboxController.updateLetterboxSurfaceVisibility(
                        updateLetterboxSurfaceVisibility(
                            key,
                            startTransaction,
                            isTopActivityLetterboxed
                        )
                    }
                letterboxController.dump()
                    dump()
                }
            }
        }
    }

    private fun logV(msg: String) {
        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, msg)
    }
}
+151 −0
Original line number Diff line number Diff line
/*
 * 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.view.SurfaceControl
import android.view.SurfaceControl.Transaction
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 a single letterbox surface.
 */
@WMSingleton
class SingleSurfaceLetterboxController @Inject constructor(
    private val letterboxConfiguration: LetterboxConfiguration
) : LetterboxController {

    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, SurfaceControl>()

    /**
     * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
     */
    override fun createLetterboxSurface(
        key: LetterboxKey,
        transaction: Transaction,
        parentLeash: SurfaceControl
    ) {
        letterboxMap.runOnItem(key, onMissed = { k, m ->
            m[k] =
                SurfaceControl.Builder()
                    .setName("ShellLetterboxSurface-$key")
                    .setHidden(true)
                    .setColorLayer()
                    .setParent(parentLeash)
                    .setCallsite("LetterboxController-createLetterboxSurface")
                    .build().apply {
                        transaction.setLayer(
                            this,
                            TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
                        ).setColorSpaceAgnostic(this, true)
                            .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
                    }
        })
    }

    /**
     * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
     */
    override fun destroyLetterboxSurface(
        key: LetterboxKey,
        transaction: Transaction
    ) {
        letterboxMap.runOnItem(key, onFound = { item ->
            item.run {
                transaction.remove(this)
            }
        })
        letterboxMap.remove(key)
    }

    /**
     * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
     */
    override fun updateLetterboxSurfaceVisibility(
        key: LetterboxKey,
        transaction: Transaction,
        visible: Boolean
    ) {
        letterboxMap.runOnItem(key, onFound = { item ->
            item.run {
                transaction.setVisibility(this, visible)
            }
        })
    }

    /**
     * Updates the bounds for the letterbox surfaces for given displayId/taskId.
     */
    override fun updateLetterboxSurfaceBounds(
        key: LetterboxKey,
        transaction: Transaction,
        taskBounds: Rect
    ) {
        letterboxMap.runOnItem(key, onFound = { item ->
            item.run {
                transaction.moveAndCrop(this, taskBounds)
            }
        })
    }

    override fun dump() {
        ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
    }

    /*
     * Executes [onFound] on the [SurfaceControl] if present or [onMissed] if not present.
     */
    private fun MutableMap<LetterboxKey, SurfaceControl>.runOnItem(
        key: LetterboxKey,
        onFound: (SurfaceControl) -> Unit = { _ -> },
        onMissed: (
            LetterboxKey,
            MutableMap<LetterboxKey, SurfaceControl>
        ) -> Unit = { _, _ -> }
    ) {
        this[key]?.let {
            return onFound(it)
        }
        return onMissed(key, this)
    }

    private fun Transaction.moveAndCrop(
        surface: SurfaceControl,
        rect: Rect
    ): Transaction =
        setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
            .setWindowCrop(
                surface,
                rect.width(),
                rect.height()
            )
}
+6 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxController;
import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
import com.android.wm.shell.compatui.letterbox.SingleSurfaceLetterboxController;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -1316,4 +1317,9 @@ public abstract class WMShellModule {
    ) {
        return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
    }

    @WMSingleton
    @Binds
    abstract LetterboxController bindsLetterboxController(
            SingleSurfaceLetterboxController letterboxController);
}
Loading