Loading libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/ActivityLetterboxLifecycleEventFactory.kt +2 −1 Original line number Diff line number Diff line Loading @@ -64,7 +64,8 @@ class ActivityLetterboxLifecycleEventFactory( taskBounds = taskBounds, letterboxBounds = letterboxBounds, taskLeash = taskItem.containerLeash, containerToken = taskItem.containerToken containerToken = taskItem.containerToken, isTranslucent = change.isTranslucent() ) } ProtoLog.w(WM_SHELL_APP_COMPAT, "$TAG: Task not found for taskId: $taskId") Loading libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEvent.kt +8 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.compatui.letterbox.lifecycle import android.graphics.Rect import android.view.SurfaceControl import android.window.TransitionInfo.Change import android.window.TransitionInfo.FLAG_TRANSLUCENT import android.window.WindowContainerToken import com.android.wm.shell.compatui.letterbox.LetterboxKey import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventType.CLOSE Loading @@ -44,7 +45,8 @@ data class LetterboxLifecycleEvent( val letterboxBounds: Rect? = null, val containerToken: WindowContainerToken? = null, val taskLeash: SurfaceControl? = null, val isBubble: Boolean = false val isBubble: Boolean = false, val isTranslucent: Boolean = false ) /** Loading Loading @@ -80,6 +82,11 @@ fun Change.shouldSkipForLetterbox(): Boolean = */ fun Change.isActivityChange(): Boolean = activityTransitionInfo != null /** * Returns [true] if the [Change] is related to a translucent container. */ fun Change.isTranslucent() = hasFlags(FLAG_TRANSLUCENT) /** Returns [true] if the Task hosts Activities */ fun Change.isChangeForALeafTask(): Boolean = taskInfo?.appCompatTaskInfo?.isLeafTask() ?: false libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/TaskInfoLetterboxLifecycleEventFactory.kt +2 −1 Original line number Diff line number Diff line Loading @@ -50,7 +50,8 @@ class TaskInfoLetterboxLifecycleEventFactory : LetterboxLifecycleEventFactory { letterboxBounds = letterboxBounds, containerToken = ti.token, taskLeash = change.leash, isBubble = ti.isAppBubble isBubble = ti.isAppBubble, isTranslucent = change.isTranslucent() ) } return null Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/ActivityLetterboxLifecycleEventFactoryTest.kt +30 −22 Original line number Diff line number Diff line Loading @@ -25,10 +25,14 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoState 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 import java.util.function.Consumer import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue /** * Tests for [ActivityLetterboxLifecycleEventFactory]. Loading @@ -48,7 +52,7 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { // Empty Change } validateCanHandle { canHandle -> assert(canHandle == false) assertFalse(canHandle) } } } Loading @@ -58,14 +62,20 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { fun `Read Task bounds from endAbsBounds in Change`() { runTestScenario { r -> testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) { val testLeash = mock<SurfaceControl>() val testToken = mock<WindowContainerToken>() inputChange { activityTransitionInfo { taskId = 10 } endAbsBounds = Rect(100, 50, 2000, 1500) } r.addToTaskRepository(10, LetterboxTaskInfoState(testToken, testLeash)) validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event?.taskBounds == Rect(0, 0, 1900, 1450)) assertEquals(Rect(0, 0, 1900, 1450), event?.taskBounds) } } } Loading @@ -75,20 +85,24 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { fun `Read Letterbox bounds from activityTransitionInfo and endAbsBounds in Change`() { runTestScenario { r -> testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) { val testLeash = mock<SurfaceControl>() val testToken = mock<WindowContainerToken>() inputChange { endAbsBounds = Rect(100, 50, 2000, 1500) activityTransitionInfo { taskId = 10 appCompatTransitionInfo { letterboxBounds = Rect(500, 50, 1500, 800) } } } r.addToTaskRepository(10, LetterboxTaskInfoState(testToken, testLeash)) validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event?.taskBounds == Rect(0, 0, 1900, 1450)) assert(event?.letterboxBounds == Rect(400, 0, 1400, 750)) assertEquals(Rect(0, 0, 1900, 1450), event?.taskBounds) assertEquals(Rect(400, 0, 1400, 750), event?.letterboxBounds) } } } Loading @@ -102,18 +116,16 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { val testToken = mock<WindowContainerToken>() r.addToTaskRepository(10, LetterboxTaskInfoState(testToken, testLeash)) inputChange { leash { testLeash } token { testToken } runningTaskInfo { ti -> ti.taskId = 10 activityTransitionInfo { taskId = 10 } } validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event?.taskLeash == testLeash) assert(event?.containerToken == testToken) assertEquals(testLeash, event?.taskLeash) assertEquals(testToken, event?.containerToken) } } } Loading @@ -123,20 +135,16 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { fun `Event is null if repository has no task data`() { runTestScenario { r -> testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) { val testLeash = mock<SurfaceControl>() val testToken = mock<WindowContainerToken>() inputChange { leash { testLeash } token { testToken } runningTaskInfo { ti -> ti.taskId = 10 activityTransitionInfo { taskId = 10 } } validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event == null) assertNull(event) } } } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEventTest.kt 0 → 100644 +154 −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.testing.AndroidTestingRunner import android.view.WindowManager import android.window.TransitionInfo.FLAG_TRANSLUCENT import androidx.test.filters.SmallTest import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventType.CLOSE import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventType.OPEN import com.android.wm.shell.util.testLetterboxLifecycleEvent import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue import org.junit.Test import org.junit.runner.RunWith /** * Tests for [LetterboxLifecycleEvent]. * * Build/Install/Run: * atest WMShellUnitTests:LetterboxLifecycleEventTest */ @RunWith(AndroidTestingRunner::class) @SmallTest class LetterboxLifecycleEventTest { @Test fun `asLetterboxLifecycleEventType returns the right type for OPEN modes`() { testLetterboxLifecycleEvent { inputChange { mode = WindowManager.TRANSIT_OPEN } useChange { change -> assertEquals(OPEN, change.asLetterboxLifecycleEventType()) } inputChange { mode = WindowManager.TRANSIT_TO_FRONT } useChange { change -> assertEquals(OPEN, change.asLetterboxLifecycleEventType()) } inputChange { mode = WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION } useChange { change -> assertEquals(OPEN, change.asLetterboxLifecycleEventType()) } } } @Test fun `asLetterboxLifecycleEventType returns the right type for CLOSE modes`() { testLetterboxLifecycleEvent { inputChange { mode = WindowManager.TRANSIT_CLOSE } useChange { change -> assertEquals(CLOSE, change.asLetterboxLifecycleEventType()) } inputChange { mode = WindowManager.TRANSIT_TO_BACK } useChange { change -> assertEquals(CLOSE, change.asLetterboxLifecycleEventType()) } } } @Test fun `isActivityChange returns true if activityTransitionInfo is present`() { testLetterboxLifecycleEvent { inputChange { } useChange { change -> assertFalse(change.isActivityChange()) } inputChange { activityTransitionInfo { } } useChange { change -> assertTrue(change.isActivityChange()) } } } @Test fun `isChangeForALeafTask returns true if the task is a leaf`() { testLetterboxLifecycleEvent { inputChange { } useChange { change -> assertFalse(change.isChangeForALeafTask()) } inputChange { runningTaskInfo { } } useChange { change -> assertFalse(change.isChangeForALeafTask()) } inputChange { runningTaskInfo { ti -> activityTransitionInfo { } } } useChange { change -> assertFalse(change.isChangeForALeafTask()) } inputChange { runningTaskInfo { ti -> ti.appCompatTaskInfo.setIsLeafTask(true) } } useChange { change -> assertTrue(change.isChangeForALeafTask()) } } } @Test fun `isTranslucent returns true if the FLAG_TRANSLUCENT flag is present in Change`() { testLetterboxLifecycleEvent { inputChange { } useChange { change -> assert(!change.isTranslucent()) } inputChange { flags = FLAG_TRANSLUCENT } useChange { change -> assert(!change.isTranslucent()) } } } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/ActivityLetterboxLifecycleEventFactory.kt +2 −1 Original line number Diff line number Diff line Loading @@ -64,7 +64,8 @@ class ActivityLetterboxLifecycleEventFactory( taskBounds = taskBounds, letterboxBounds = letterboxBounds, taskLeash = taskItem.containerLeash, containerToken = taskItem.containerToken containerToken = taskItem.containerToken, isTranslucent = change.isTranslucent() ) } ProtoLog.w(WM_SHELL_APP_COMPAT, "$TAG: Task not found for taskId: $taskId") Loading
libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEvent.kt +8 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.wm.shell.compatui.letterbox.lifecycle import android.graphics.Rect import android.view.SurfaceControl import android.window.TransitionInfo.Change import android.window.TransitionInfo.FLAG_TRANSLUCENT import android.window.WindowContainerToken import com.android.wm.shell.compatui.letterbox.LetterboxKey import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventType.CLOSE Loading @@ -44,7 +45,8 @@ data class LetterboxLifecycleEvent( val letterboxBounds: Rect? = null, val containerToken: WindowContainerToken? = null, val taskLeash: SurfaceControl? = null, val isBubble: Boolean = false val isBubble: Boolean = false, val isTranslucent: Boolean = false ) /** Loading Loading @@ -80,6 +82,11 @@ fun Change.shouldSkipForLetterbox(): Boolean = */ fun Change.isActivityChange(): Boolean = activityTransitionInfo != null /** * Returns [true] if the [Change] is related to a translucent container. */ fun Change.isTranslucent() = hasFlags(FLAG_TRANSLUCENT) /** Returns [true] if the Task hosts Activities */ fun Change.isChangeForALeafTask(): Boolean = taskInfo?.appCompatTaskInfo?.isLeafTask() ?: false
libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/lifecycle/TaskInfoLetterboxLifecycleEventFactory.kt +2 −1 Original line number Diff line number Diff line Loading @@ -50,7 +50,8 @@ class TaskInfoLetterboxLifecycleEventFactory : LetterboxLifecycleEventFactory { letterboxBounds = letterboxBounds, containerToken = ti.token, taskLeash = change.leash, isBubble = ti.isAppBubble isBubble = ti.isAppBubble, isTranslucent = change.isTranslucent() ) } return null Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/ActivityLetterboxLifecycleEventFactoryTest.kt +30 −22 Original line number Diff line number Diff line Loading @@ -25,10 +25,14 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoRepository import com.android.wm.shell.compatui.letterbox.state.LetterboxTaskInfoState 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 import java.util.function.Consumer import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertTrue /** * Tests for [ActivityLetterboxLifecycleEventFactory]. Loading @@ -48,7 +52,7 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { // Empty Change } validateCanHandle { canHandle -> assert(canHandle == false) assertFalse(canHandle) } } } Loading @@ -58,14 +62,20 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { fun `Read Task bounds from endAbsBounds in Change`() { runTestScenario { r -> testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) { val testLeash = mock<SurfaceControl>() val testToken = mock<WindowContainerToken>() inputChange { activityTransitionInfo { taskId = 10 } endAbsBounds = Rect(100, 50, 2000, 1500) } r.addToTaskRepository(10, LetterboxTaskInfoState(testToken, testLeash)) validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event?.taskBounds == Rect(0, 0, 1900, 1450)) assertEquals(Rect(0, 0, 1900, 1450), event?.taskBounds) } } } Loading @@ -75,20 +85,24 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { fun `Read Letterbox bounds from activityTransitionInfo and endAbsBounds in Change`() { runTestScenario { r -> testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) { val testLeash = mock<SurfaceControl>() val testToken = mock<WindowContainerToken>() inputChange { endAbsBounds = Rect(100, 50, 2000, 1500) activityTransitionInfo { taskId = 10 appCompatTransitionInfo { letterboxBounds = Rect(500, 50, 1500, 800) } } } r.addToTaskRepository(10, LetterboxTaskInfoState(testToken, testLeash)) validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event?.taskBounds == Rect(0, 0, 1900, 1450)) assert(event?.letterboxBounds == Rect(400, 0, 1400, 750)) assertEquals(Rect(0, 0, 1900, 1450), event?.taskBounds) assertEquals(Rect(400, 0, 1400, 750), event?.letterboxBounds) } } } Loading @@ -102,18 +116,16 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { val testToken = mock<WindowContainerToken>() r.addToTaskRepository(10, LetterboxTaskInfoState(testToken, testLeash)) inputChange { leash { testLeash } token { testToken } runningTaskInfo { ti -> ti.taskId = 10 activityTransitionInfo { taskId = 10 } } validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event?.taskLeash == testLeash) assert(event?.containerToken == testToken) assertEquals(testLeash, event?.taskLeash) assertEquals(testToken, event?.containerToken) } } } Loading @@ -123,20 +135,16 @@ class ActivityLetterboxLifecycleEventFactoryTest : ShellTestCase() { fun `Event is null if repository has no task data`() { runTestScenario { r -> testLetterboxLifecycleEventFactory(r.getLetterboxLifecycleEventFactory()) { val testLeash = mock<SurfaceControl>() val testToken = mock<WindowContainerToken>() inputChange { leash { testLeash } token { testToken } runningTaskInfo { ti -> ti.taskId = 10 activityTransitionInfo { taskId = 10 } } validateCanHandle { canHandle -> assert(canHandle == false) assertTrue(canHandle) } validateCreateLifecycleEvent { event -> assert(event == null) assertNull(event) } } } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/lifecycle/LetterboxLifecycleEventTest.kt 0 → 100644 +154 −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.testing.AndroidTestingRunner import android.view.WindowManager import android.window.TransitionInfo.FLAG_TRANSLUCENT import androidx.test.filters.SmallTest import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventType.CLOSE import com.android.wm.shell.compatui.letterbox.lifecycle.LetterboxLifecycleEventType.OPEN import com.android.wm.shell.util.testLetterboxLifecycleEvent import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue import org.junit.Test import org.junit.runner.RunWith /** * Tests for [LetterboxLifecycleEvent]. * * Build/Install/Run: * atest WMShellUnitTests:LetterboxLifecycleEventTest */ @RunWith(AndroidTestingRunner::class) @SmallTest class LetterboxLifecycleEventTest { @Test fun `asLetterboxLifecycleEventType returns the right type for OPEN modes`() { testLetterboxLifecycleEvent { inputChange { mode = WindowManager.TRANSIT_OPEN } useChange { change -> assertEquals(OPEN, change.asLetterboxLifecycleEventType()) } inputChange { mode = WindowManager.TRANSIT_TO_FRONT } useChange { change -> assertEquals(OPEN, change.asLetterboxLifecycleEventType()) } inputChange { mode = WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION } useChange { change -> assertEquals(OPEN, change.asLetterboxLifecycleEventType()) } } } @Test fun `asLetterboxLifecycleEventType returns the right type for CLOSE modes`() { testLetterboxLifecycleEvent { inputChange { mode = WindowManager.TRANSIT_CLOSE } useChange { change -> assertEquals(CLOSE, change.asLetterboxLifecycleEventType()) } inputChange { mode = WindowManager.TRANSIT_TO_BACK } useChange { change -> assertEquals(CLOSE, change.asLetterboxLifecycleEventType()) } } } @Test fun `isActivityChange returns true if activityTransitionInfo is present`() { testLetterboxLifecycleEvent { inputChange { } useChange { change -> assertFalse(change.isActivityChange()) } inputChange { activityTransitionInfo { } } useChange { change -> assertTrue(change.isActivityChange()) } } } @Test fun `isChangeForALeafTask returns true if the task is a leaf`() { testLetterboxLifecycleEvent { inputChange { } useChange { change -> assertFalse(change.isChangeForALeafTask()) } inputChange { runningTaskInfo { } } useChange { change -> assertFalse(change.isChangeForALeafTask()) } inputChange { runningTaskInfo { ti -> activityTransitionInfo { } } } useChange { change -> assertFalse(change.isChangeForALeafTask()) } inputChange { runningTaskInfo { ti -> ti.appCompatTaskInfo.setIsLeafTask(true) } } useChange { change -> assertTrue(change.isChangeForALeafTask()) } } } @Test fun `isTranslucent returns true if the FLAG_TRANSLUCENT flag is present in Change`() { testLetterboxLifecycleEvent { inputChange { } useChange { change -> assert(!change.isTranslucent()) } inputChange { flags = FLAG_TRANSLUCENT } useChange { change -> assert(!change.isTranslucent()) } } } }