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

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

[43/n] Define LetterboxLifecycleEventFactory

The event triggering some Letterbox Surface lifecycle operation
can come from different sources which are abstracted with
LetterboxLifecycleEventFactory.

MultiLetterboxLifecycleEventFactory implements the same interface
adding a Chain of Responsibility logic which delegates the
creation of the LetterboxLifecycleEvent to the first object
that has all the data to do it.

LetterboxLifecycleEventFactoryUtils provides some utilities to
simplify the test of any LetterboxLifecycleEventFactory
implementation and starts the definition of different
TestInputBuilder to be used when common objects need to be
created (e.g. Change, TaskInfo, etc).

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

Change-Id: I4c2ad7177a73cd7d507d7a5ec06a7295fcb553dc
parent 6cc1636c
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -38,8 +38,8 @@ enum class LetterboxLifecycleEventType {
 */
data class LetterboxLifecycleEvent(
    val type: LetterboxLifecycleEventType = NONE,
    val taskId: Int,
    val displayId: Int,
    val taskId: Int = -1,
    val displayId: Int = -1,
    val taskBounds: Rect,
    val letterboxBounds: Rect? = null,
    val letterboxActivityToken: WindowContainerToken? = null,
+39 −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

/**
 * Abstracts the different way we can use to create a [LetterboxLifecycleEvent]
 * from a [TransitionInfo.Change].
 */
interface LetterboxLifecycleEventFactory {

    /**
     * @return [true] in case the specific implementation can handle the Change and return a
     *         [LetterboxLifecycleEvent] from it.
     */
    fun canHandle(change: Change): Boolean

    /**
     * If [#canHandle()] returns [true], this builds the [LetterboxLifecycleEvent] from the
     * [TransitionInfo.Change] in input. The [null] value represents a no-op and this should be
     * the value to return when [#canHandle()] returns [false].
     */
    fun createLifecycleEvent(change: Change): LetterboxLifecycleEvent?
}
+40 −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

/**
 * [LetterboxLifecycleEventFactory] implementation which aggregates other implementation in a
 * Chain of Responsibility logic. The [candidates] are evaluated in order.
 */
class MultiLetterboxLifecycleEventFactory(
    private val candidates: List<LetterboxLifecycleEventFactory>
) : LetterboxLifecycleEventFactory {

    /**
     * @return [true] in case any of the [candidates] can handle the [Change] in input.
     */
    override fun canHandle(change: Change): Boolean = candidates.any { it.canHandle(change) }

    /**
     * @return The [LetterboxLifecycleEvent] from the selected candidate which is the first in
     *         [candidates], if any, which [@canHandle] the [Change].
     */
    override fun createLifecycleEvent(change: Change): LetterboxLifecycleEvent? =
        candidates.firstOrNull { it.canHandle(change) }?.createLifecycleEvent(change)
}
+51 −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.window.TransitionInfo.Change

/**
 * Fake [LetterboxLifecycleEventFactory] implementation.
 */
class FakeLetterboxLifecycleEventFactory(
    private val canHandleReturn: Boolean = true,
    private val eventToReturn: LetterboxLifecycleEvent? = null
) : LetterboxLifecycleEventFactory {

    companion object {
        @JvmStatic
        val FAKE_EVENT = LetterboxLifecycleEvent(taskBounds = Rect())
    }

    var canHandleInvokeTimes: Int = 0
    var lastCanHandleChange: Change? = null
    var createLifecycleEventInvokeTimes: Int = 0
    var lastCreateLifecycleEventChange: Change? = null

    override fun canHandle(change: Change): Boolean {
        canHandleInvokeTimes++
        lastCanHandleChange = change
        return canHandleReturn
    }

    override fun createLifecycleEvent(change: Change): LetterboxLifecycleEvent? {
        createLifecycleEventInvokeTimes++
        lastCreateLifecycleEventChange = change
        return eventToReturn
    }
}
+176 −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.lifecycle.FakeLetterboxLifecycleEventFactory.Companion.FAKE_EVENT
import com.android.wm.shell.util.testLetterboxLifecycleEventFactory
import org.junit.Test
import org.junit.runner.RunWith
import java.util.function.Consumer

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

    @Test
    fun `canHandle is invoked until a first true is found`() {
        runTestScenario { r ->
            testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) {
                r.addCandidate(canHandleReturn = false)
                r.addCandidate(canHandleReturn = true)
                r.addCandidate(canHandleReturn = false)
                inputChange {
                    // No specific Change initialization required.
                }
                validateCanHandle { canHandler ->
                    assert(canHandler == true)
                    r.assertOnCandidate(0) { f ->
                        assert(f.canHandleInvokeTimes == 1)
                    }
                    r.assertOnCandidate(1) { f ->
                        assert(f.canHandleInvokeTimes == 1)
                    }
                    r.assertOnCandidate(2) { f ->
                        assert(f.canHandleInvokeTimes == 0)
                    }
                }
            }
        }
    }

    @Test
    fun `canHandle returns false if no one can handle`() {
        runTestScenario { r ->
            testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) {
                r.addCandidate(canHandleReturn = false)
                r.addCandidate(canHandleReturn = false)
                r.addCandidate(canHandleReturn = false)
                inputChange {
                    // No specific Change initialization required.
                }
                validateCanHandle { canHandler ->
                    assert(canHandler == false)
                }
            }
        }
    }

    @Test
    fun `No LetterboxLifecycleEventFactory used if no one can handle`() {
        runTestScenario { r ->
            testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) {
                r.addCandidate(canHandleReturn = false)
                r.addCandidate(canHandleReturn = false)
                r.addCandidate(canHandleReturn = false)
                inputChange {
                    // No specific Change initialization required.
                }
                validateCreateLifecycleEvent { event ->
                    assert(event == null)
                    for (pos in 0..2) {
                        r.assertOnCandidate(pos) { f ->
                            assert(f.canHandleInvokeTimes == 1)
                            assert(f.createLifecycleEventInvokeTimes == 0)
                        }
                    }
                }
            }
        }
    }

    @Test
    fun `Only the one which can handle creates the LetterboxLifecycleEvent`() {
        runTestScenario { r ->
            testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) {
                r.addCandidate(canHandleReturn = false)
                r.addCandidate(
                    canHandleReturn = true,
                    eventToReturn = LetterboxLifecycleEvent(
                        taskId = 30,
                        taskBounds = Rect(1, 2, 3, 4)
                    )
                )
                r.addCandidate(canHandleReturn = false)
                inputChange {
                    // No specific Change initialization required.
                }
                validateCreateLifecycleEvent { event ->
                    assert(event != null)
                    r.assertOnCandidate(0) { f ->
                        assert(f.canHandleInvokeTimes == 1)
                        assert(f.createLifecycleEventInvokeTimes == 0)
                    }
                    r.assertOnCandidate(1) { f ->
                        assert(f.canHandleInvokeTimes == 1)
                        assert(f.createLifecycleEventInvokeTimes == 1)
                    }
                    r.assertOnCandidate(2) { f ->
                        assert(f.canHandleInvokeTimes == 0)
                        assert(f.createLifecycleEventInvokeTimes == 0)
                    }
                    assert(event?.taskId == 30)
                    assert(event?.taskBounds == Rect(1, 2, 3, 4))
                }
            }
        }
    }

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

    /**
     * Robot contextual to [MultiLetterboxLifecycleEventFactory].
     */
    class LetterboxLifecycleControllerImplRobotTest {


        private val candidates = mutableListOf<FakeLetterboxLifecycleEventFactory>()

        fun getLetterboxLifecycleEventFactory(): () -> LetterboxLifecycleEventFactory = {
            MultiLetterboxLifecycleEventFactory(candidates)
        }

        fun addCandidate(
            canHandleReturn: Boolean = true,
            eventToReturn: LetterboxLifecycleEvent = FAKE_EVENT
        ) {
            candidates.add(FakeLetterboxLifecycleEventFactory(canHandleReturn, eventToReturn))
        }

        fun assertOnCandidate(
            position: Int,
            consumer: (FakeLetterboxLifecycleEventFactory) -> Unit
        ) {
            consumer(candidates[position])
        }
    }
}
Loading