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

Commit 1b2b2ba1 authored by Massimo Carli's avatar Massimo Carli
Browse files

[54/n] Implement ActivityLetterboxLifecycleEventFactory.

The ActivityLetterboxLifecycleEventFactory allows to create a LetterboxLifecycleEvent
from the information in ActivityTaskInfo.

Flag: com.android.window.flags.app_compat_refactoring
Bug: 409043134
Test: atest WMShellUnitTests:ActivityLetterboxLifecycleEventFactoryTest

Change-Id: I24db7165a824f18fc94acae06de1219456b5ff8e
parent 001d7fad
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.lifecycle

import android.window.TransitionInfo.Change
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository

/**
 * [LetterboxLifecycleEventFactory] implementation which creates a [LetterboxLifecycleEvent] from
 * a [TransitionInfo.Change] using a [ActivityTransitionInfo] when present.
 */
class ActivityLetterboxLifecycleEventFactory(
    private val taskRepository: LetterboxTaskInfoRepository
) : LetterboxLifecycleEventFactory {
    override fun canHandle(change: Change): Boolean = change.activityTransitionInfo != null

    override fun createLifecycleEvent(change: Change): LetterboxLifecycleEvent {
        val activityTransitionInfo = change.activityTransitionInfo
        val taskBounds = change.endAbsBounds

        val letterboxBoundsTmp = activityTransitionInfo?.appCompatTransitionInfo?.letterboxBounds
        val taskId = activityTransitionInfo?.taskId ?: -1

        val isLetterboxed = letterboxBoundsTmp != taskBounds
        // Letterbox bounds are null when the activity is not letterboxed.
        val letterboxBounds = if (isLetterboxed) letterboxBoundsTmp else null
        val taskToken = taskRepository.find(taskId)?.containerToken
        val taskLeash = taskRepository.find(taskId)?.containerLeash
        return LetterboxLifecycleEvent(
            type = change.asLetterboxLifecycleEventType(),
            taskId = taskId,
            taskBounds = taskBounds,
            letterboxBounds = letterboxBounds,
            taskLeash = taskLeash,
            containerToken = taskToken
        )
    }
}
+6 −1
Original line number Diff line number Diff line
@@ -21,12 +21,14 @@ import android.annotation.NonNull;
import com.android.wm.shell.compatui.letterbox.DelegateLetterboxTransitionObserver;
import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy;
import com.android.wm.shell.compatui.letterbox.MixedLetterboxController;
import com.android.wm.shell.compatui.letterbox.lifecycle.ActivityLetterboxLifecycleEventFactory;
import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleController;
import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleControllerImpl;
import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventFactory;
import com.android.wm.shell.compatui.letterbox.lifecycle.MultiLetterboxLifecycleEventFactory;
import com.android.wm.shell.compatui.letterbox.lifecycle.SkipLetterboxLifecycleEventFactory;
import com.android.wm.shell.compatui.letterbox.lifecycle.TaskInfoLetterboxLifecycleEventFactory;
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;

@@ -56,13 +58,16 @@ public abstract class LetterboxModule {
    @WMSingleton
    @Provides
    static LetterboxLifecycleEventFactory provideLetterboxLifecycleEventFactory(
            @NonNull SkipLetterboxLifecycleEventFactory skipLetterboxLifecycleEventFactory
            @NonNull SkipLetterboxLifecycleEventFactory skipLetterboxLifecycleEventFactory,
            @NonNull LetterboxTaskInfoRepository letterboxTaskInfoRepository
    ) {
        // The order of the LetterboxLifecycleEventFactory implementation matters because the
        // first that can handle a Change will be chosen for the LetterboxLifecycleEvent creation.
        return new MultiLetterboxLifecycleEventFactory(List.of(
                // Filters out transitions not related to Letterboxing.
                skipLetterboxLifecycleEventFactory,
                // Handle Transition for Activities
                new ActivityLetterboxLifecycleEventFactory(letterboxTaskInfoRepository),
                // Creates a LetterboxLifecycleEvent in case of Task transitions.
                new TaskInfoLetterboxLifecycleEventFactory()
        ));
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.lifecycle

import android.graphics.Rect
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository
import com.android.wm.shell.util.testLetterboxLifecycleEventFactory
import java.util.function.Consumer
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

/**
 * Tests for [ActivityLetterboxLifecycleEventFactory].
 *
 * Build/Install/Run:
 *  atest WMShellUnitTests:ActivityLetterboxLifecycleEventFactoryTest
 */
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() {

    @Test
    fun `Change without ActivityTransitionInfo cannot create the event`() {
        runTestScenario { r ->
            testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) {
                inputChange {
                    // Empty Change
                }
                validateCanHandle { canHandle ->
                    assert(canHandle == false)
                }
            }
        }
    }

    @Test
    fun `Read Task bounds from endAbsBounds in Change`() {
        runTestScenario { r ->
            testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) {
                inputChange {
                    endAbsBounds = Rect(0, 0, 500, 1000)
                }
                validateCanHandle { canHandle ->
                    assert(canHandle == false)
                }
                validateCreateLifecycleEvent { event ->
                    assert(event?.taskBounds == Rect(0, 0, 500, 1000))
                }
            }
        }
    }

    /**
     * Runs a test scenario providing a Robot.
     */
    fun runTestScenario(consumer: Consumer<ActivityLetterboxLifecycleEventFactoryRobotTest>) {
        val robot = ActivityLetterboxLifecycleEventFactoryRobotTest()
        consumer.accept(robot)
    }

    /**
     * Robot contextual to [ActivityLetterboxLifecycleEventFactory].
     */
    class ActivityLetterboxLifecycleEventFactoryRobotTest {

        private val letterboxTaskInfoRepository: LetterboxTaskInfoRepository =
            mock<LetterboxTaskInfoRepository>()

        fun getLetterboxLifecycleEventFactory(): () -> LetterboxLifecycleEventFactory = {
            ActivityLetterboxLifecycleEventFactory(letterboxTaskInfoRepository)
        }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.graphics.Point
import android.graphics.Rect
import android.view.SurfaceControl
import android.window.ActivityTransitionInfo
import android.window.TransitionInfo.Change
import android.window.WindowContainerToken
import org.mockito.kotlin.mock
@@ -40,6 +41,7 @@ class ChangeTestInputBuilder : TestInputBuilder<Change> {
    private val inputParams = InputParams()
    var endAbsBounds: Rect? = null
    var endRelOffset: Point? = null
    var activityTransitionInfo: ActivityTransitionInfo? = null

    data class InputParams(
        var token: WindowContainerToken = mock<WindowContainerToken>(),
@@ -86,6 +88,7 @@ class ChangeTestInputBuilder : TestInputBuilder<Change> {
            this@ChangeTestInputBuilder.endRelOffset?.let {
                this@apply.endRelOffset.set(endRelOffset)
            }
            activityTransitionInfo = this@ChangeTestInputBuilder.activityTransitionInfo
        }
    }
}
+13 −34
Original line number Diff line number Diff line
@@ -25,13 +25,10 @@ import com.android.wm.shell.ShellTaskOrganizer.TaskVanishedListener
@DslMarker
annotation class TaskListenerTestTagMarker

@TaskListenerTestTagMarker
class TaskAppearedListenerTestContext(
    private val testSubjectFactory: () -> TaskAppearedListener
) {
// Base class for the TaskListener interfaces Test Context.
open class BaseTaskListenerTestContext<TL> {

    private lateinit var inputTaskInfo: RunningTaskInfo
    private var inputLeash: SurfaceControl = SurfaceControl()
    protected lateinit var inputTaskInfo: RunningTaskInfo

    fun runningTaskInfo(
        builder: RunningTaskInfoTestInputBuilder.(RunningTaskInfo) -> Unit
@@ -43,6 +40,14 @@ class TaskAppearedListenerTestContext(
            inputTaskInfo = this
        }
    }
}

@TaskListenerTestTagMarker
class TaskAppearedListenerTestContext(
    private val testSubjectFactory: () -> TaskAppearedListener
) : BaseTaskListenerTestContext<TaskAppearedListener>() {

    private var inputLeash: SurfaceControl = SurfaceControl()

    fun leash(builder: SurfaceControlTestInputBuilder.() -> SurfaceControl): SurfaceControl {
        val binderObj = SurfaceControlTestInputBuilder()
@@ -73,20 +78,7 @@ fun testTaskAppearedListener(
@TaskListenerTestTagMarker
class TaskVanishedListenerTestContext(
    private val testSubjectFactory: () -> TaskVanishedListener
) {

    private lateinit var inputTaskInfo: RunningTaskInfo

    fun runningTaskInfo(
        builder: RunningTaskInfoTestInputBuilder.(RunningTaskInfo) -> Unit
    ): RunningTaskInfo {
        val runningTaskInfoObj = RunningTaskInfoTestInputBuilder()
        return RunningTaskInfo().also {
            runningTaskInfoObj.builder(it)
        }.apply {
            inputTaskInfo = this
        }
    }
) : BaseTaskListenerTestContext<TaskVanishedListener>() {

    fun validateOnTaskVanished(verifier: () -> Unit) {
        // We execute the test subject using the input
@@ -110,20 +102,7 @@ fun testTaskVanishedListener(
@TaskListenerTestTagMarker
class TaskInfoChangedListenerTestContext(
    private val testSubjectFactory: () -> TaskInfoChangedListener
) {

    private lateinit var inputTaskInfo: RunningTaskInfo

    fun runningTaskInfo(
        builder: RunningTaskInfoTestInputBuilder.(RunningTaskInfo) -> Unit
    ): RunningTaskInfo {
        val runningTaskInfoObj = RunningTaskInfoTestInputBuilder()
        return RunningTaskInfo().also {
            runningTaskInfoObj.builder(it)
        }.apply {
            inputTaskInfo = this
        }
    }
) : BaseTaskListenerTestContext<TaskInfoChangedListener>() {

    fun validateOnTaskInfoChanged(verifier: () -> Unit) {
        // We execute the test subject using the input