Loading libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEvent.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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, Loading libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEventFactory.kt 0 → 100644 +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? } libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/MultiLetterboxLifecycleEventFactory.kt 0 → 100644 +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) } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/FakeLetterboxLifecycleEventFactory.kt 0 → 100644 +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 } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/MultiLetterboxLifecycleEventFactoryTest.kt 0 → 100644 +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
libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEvent.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEventFactory.kt 0 → 100644 +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? }
libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/MultiLetterboxLifecycleEventFactory.kt 0 → 100644 +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) }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/FakeLetterboxLifecycleEventFactory.kt 0 → 100644 +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 } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/MultiLetterboxLifecycleEventFactoryTest.kt 0 → 100644 +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]) } } }