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

Commit 651542d0 authored by Massimo Carli's avatar Massimo Carli
Browse files

[11/n] Implement LetterboxController for multiple surfaces

Create MultiSurfaceLetterboxController to use when required by
a specific strategy (e.g. when rounded corners are present or
when the activity is transparent). The strategy will be defined
in a follow-up CL.

Flag: com.android.window.flags.app_compat_refactoring
Fix: 377857898
Test: atest WmTests:AppCompatUtilsTest

Change-Id: Iae4d0e852b4ee645e1730886da31b2fef54bd1e9
parent e8d7cf37
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.TaskInfo.PROPERTY_VALUE_UNSET;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;

@@ -68,6 +69,14 @@ public class AppCompatTaskInfo implements Parcelable {
     */
    public int topActivityLetterboxAppWidth = PROPERTY_VALUE_UNSET;

    /**
     * Contains the top activity bounds when the activity is letterboxed.
     * It's {@code null} if there's no top activity in the task or it's not letterboxed.
     */
    // TODO(b/379824541) Remove duplicate information.
    @Nullable
    public Rect topActivityLetterboxBounds;

    /**
     * Stores camera-related app compat information about a particular Task.
     */
@@ -378,6 +387,7 @@ public class AppCompatTaskInfo implements Parcelable {
        topActivityLetterboxHeight = source.readInt();
        topActivityLetterboxAppWidth = source.readInt();
        topActivityLetterboxAppHeight = source.readInt();
        topActivityLetterboxBounds = source.readTypedObject(Rect.CREATOR);
        cameraCompatTaskInfo = source.readTypedObject(CameraCompatTaskInfo.CREATOR);
    }

@@ -393,6 +403,7 @@ public class AppCompatTaskInfo implements Parcelable {
        dest.writeInt(topActivityLetterboxHeight);
        dest.writeInt(topActivityLetterboxAppWidth);
        dest.writeInt(topActivityLetterboxAppHeight);
        dest.writeTypedObject(topActivityLetterboxBounds, flags);
        dest.writeTypedObject(cameraCompatTaskInfo, flags);
    }

@@ -415,6 +426,7 @@ public class AppCompatTaskInfo implements Parcelable {
                + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
                + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
                + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
                + " topActivityLetterboxBounds=" + topActivityLetterboxBounds
                + " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
                + "}";
    }
+2 −1
Original line number Diff line number Diff line
@@ -58,7 +58,8 @@ interface LetterboxController {
    fun updateLetterboxSurfaceBounds(
        key: LetterboxKey,
        transaction: Transaction,
        taskBounds: Rect
        taskBounds: Rect,
        activityBounds: Rect
    )

    /**
+14 −1
Original line number Diff line number Diff line
@@ -16,5 +16,18 @@

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)

// Encapsulates the surfaces in the multiple surfaces scenario.
data class LetterboxSurfaces(
    var leftSurface: SurfaceControl? = null,
    var topSurface: SurfaceControl? = null,
    var rightSurface: SurfaceControl? = null,
    var bottomSurface: SurfaceControl? = null
) : Iterable<SurfaceControl?> {
    override fun iterator() =
        listOf(leftSurface, topSurface, rightSurface, bottomSurface).iterator()
}
+6 −1
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ class LetterboxTransitionObserver(
    companion object {
        @JvmStatic
        private val TAG = "LetterboxTransitionObserver"
        @JvmStatic
        private val EMPTY_BOUNDS = Rect()
    }

    init {
@@ -86,10 +88,13 @@ class LetterboxTransitionObserver(
                                startTransaction,
                                change.leash
                            )
                            val activityBounds =
                                ti.appCompatTaskInfo.topActivityLetterboxBounds ?: EMPTY_BOUNDS
                            updateLetterboxSurfaceBounds(
                                key,
                                startTransaction,
                                taskBounds
                                taskBounds,
                                activityBounds
                            )
                        }
                        updateLetterboxSurfaceVisibility(
+176 −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 multiple letterbox surfaces when needed.
 */
@WMSingleton
class MultiSurfaceLetterboxController @Inject constructor(
    private val letterboxBuilder: LetterboxSurfaceBuilder
) : LetterboxController {

    companion object {
        @JvmStatic
        private val TAG = "MultiSurfaceLetterboxController"
    }

    private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxSurfaces>()

    override fun createLetterboxSurface(
        key: LetterboxKey,
        transaction: Transaction,
        parentLeash: SurfaceControl
    ) {
        val surfaceBuilderFn = { position: String ->
            letterboxBuilder.createSurface(
                transaction,
                parentLeash,
                "ShellLetterboxSurface-$key-$position",
                "MultiSurfaceLetterboxController#createLetterboxSurface"
            )
        }
        letterboxMap.runOnItem(key, onMissed = { k, m ->
            m[k] = LetterboxSurfaces(
                leftSurface = surfaceBuilderFn("Left"),
                topSurface = surfaceBuilderFn("Top"),
                rightSurface = surfaceBuilderFn("Right"),
                bottomSurface = surfaceBuilderFn("Bottom"),
            )
        })
    }

    override fun destroyLetterboxSurface(
        key: LetterboxKey,
        transaction: Transaction
    ) {
        letterboxMap.runOnItem(key, onFound = { item ->
            item.forEach { s ->
                s.remove(transaction)
            }
        })
        letterboxMap.remove(key)
    }

    override fun updateLetterboxSurfaceVisibility(
        key: LetterboxKey,
        transaction: Transaction,
        visible: Boolean
    ) {
        letterboxMap.runOnItem(key, onFound = { item ->
            item.forEach { s ->
                s.setVisibility(transaction, visible)
            }
        })
    }

    override fun updateLetterboxSurfaceBounds(
        key: LetterboxKey,
        transaction: Transaction,
        taskBounds: Rect,
        activityBounds: Rect
    ) {
        letterboxMap.runOnItem(key, onFound = { item ->
            item.updateSurfacesBounds(transaction, taskBounds, activityBounds)
        })
    }

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

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

    private fun SurfaceControl?.remove(
        tx: Transaction
    ) = this?.let {
        tx.remove(this)
    }

    private fun SurfaceControl?.setVisibility(
        tx: Transaction,
        visible: Boolean
    ) = this?.let {
        tx.setVisibility(this, visible)
    }

    private fun Transaction.moveAndCrop(
        surface: SurfaceControl,
        rect: Rect
    ): Transaction =
        setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
            .setWindowCrop(
                surface,
                rect.width(),
                rect.height()
            )

    private fun LetterboxSurfaces.updateSurfacesBounds(
        tx: Transaction,
        taskBounds: Rect,
        activityBounds: Rect
    ) {
        // Update the bounds depending on the activity position.
        leftSurface?.let { s ->
            tx.moveAndCrop(
                s,
                Rect(taskBounds.left, taskBounds.top, activityBounds.left, taskBounds.bottom)
            )
        }
        rightSurface?.let { s ->
            tx.moveAndCrop(
                s,
                Rect(activityBounds.right, taskBounds.top, taskBounds.right, taskBounds.bottom)
            )
        }
        topSurface?.let { s ->
            tx.moveAndCrop(
                s,
                Rect(taskBounds.left, taskBounds.top, taskBounds.right, activityBounds.top)
            )
        }
        bottomSurface?.let { s ->
            tx.moveAndCrop(
                s,
                Rect(taskBounds.left, activityBounds.bottom, taskBounds.right, taskBounds.bottom)
            )
        }
    }
}
Loading