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

Commit aad007ba authored by Massimo Carli's avatar Massimo Carli
Browse files

[3/n] Implement Letterbox Surfaces lifecycle

Creates a TransitionObserver to observe existing Transactions to
identify events related on the letterbox surface lifecycle.

Creates LetterboxController which encapsulates operations on the
Letterbox surfaces in Shell. The current implementation uses a
single surface.

Flag: com.android.window.flags.app_compat_refactoring
Fix: 370997904
Test: m

Change-Id: I5908f110d2b0a3e464810196c13508408c662399
parent 26146d50
Loading
Loading
Loading
Loading
+150 −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.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()
            )
}
+25 −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.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
+106 −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.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()
            }
        }
    }
}
+18 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRA
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.MultiInstanceHelper;
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 @@ public abstract class WMShellModule {
    @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);
    }
}