summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt142
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt128
5 files changed, 453 insertions, 4 deletions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
new file mode 100644
index 000000000000..f9f01bcd54f3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.content.Context
+import android.graphics.Rect
+import android.view.SurfaceControl
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode
+import org.mockito.kotlin.any
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+/**
+ * Robot to test [LetterboxController] implementations.
+ */
+open class LetterboxControllerRobotTest(
+ ctx: Context,
+ controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController
+) {
+
+ companion object {
+ @JvmStatic
+ private val DISPLAY_ID = 1
+
+ @JvmStatic
+ private val TASK_ID = 20
+ }
+
+ private val letterboxConfiguration: LetterboxConfiguration
+ private val surfaceBuilder: LetterboxSurfaceBuilder
+ private val letterboxController: LetterboxController
+ private val transaction: SurfaceControl.Transaction
+ private val parentLeash: SurfaceControl
+
+ init {
+ letterboxConfiguration = LetterboxConfiguration(ctx)
+ surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+ letterboxController = controllerBuilder(surfaceBuilder)
+ transaction = getTransactionMock()
+ parentLeash = mock<SurfaceControl>()
+ spyOn(surfaceBuilder)
+ }
+
+ fun sendCreateSurfaceRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) = letterboxController.createLetterboxSurface(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction,
+ parentLeash = parentLeash
+ )
+
+ fun sendDestroySurfaceRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID
+ ) = letterboxController.destroyLetterboxSurface(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction
+ )
+
+ fun sendUpdateSurfaceVisibilityRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID,
+ visible: Boolean
+ ) = letterboxController.updateLetterboxSurfaceVisibility(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction,
+ visible = visible
+ )
+
+ fun sendUpdateSurfaceBoundsRequest(
+ displayId: Int = DISPLAY_ID,
+ taskId: Int = TASK_ID,
+ taskBounds: Rect,
+ activityBounds: Rect
+ ) = letterboxController.updateLetterboxSurfaceBounds(
+ key = LetterboxKey(displayId, taskId),
+ transaction = transaction,
+ taskBounds = taskBounds,
+ activityBounds = activityBounds
+ )
+
+ fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
+ verify(surfaceBuilder, times(times)).createSurface(
+ eq(transaction),
+ eq(parentLeash),
+ name.asAnyMode(),
+ callSite.asAnyMode(),
+ any()
+ )
+ }
+
+ fun checkTransactionRemovedInvoked(times: Int = 1) {
+ verify(transaction, times(times)).remove(any())
+ }
+
+ fun checkVisibilityUpdated(times: Int = 1, expectedVisibility: Boolean) {
+ verify(transaction, times(times)).setVisibility(any(), eq(expectedVisibility))
+ }
+
+ fun checkSurfacePositionUpdated(
+ times: Int = 1,
+ expectedX: Float = -1f,
+ expectedY: Float = -1f
+ ) {
+ verify(transaction, times(times)).setPosition(
+ any(),
+ expectedX.asAnyMode(),
+ expectedY.asAnyMode()
+ )
+ }
+
+ fun checkSurfaceSizeUpdated(times: Int = 1, expectedWidth: Int = -1, expectedHeight: Int = -1) {
+ verify(transaction, times(times)).setWindowCrop(
+ any(),
+ expectedWidth.asAnyMode(),
+ expectedHeight.asAnyMode()
+ )
+ }
+
+ fun resetTransitionTest() {
+ clearInvocations(transaction)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
index 70341b6646c2..c37913e47cca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
@@ -28,7 +28,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.verify
import org.mockito.kotlin.anyOrNull
-import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.times
@@ -85,9 +84,7 @@ class LetterboxSurfaceBuilderTest : ShellTestCase() {
init {
letterboxConfiguration = LetterboxConfiguration(ctx)
letterboxSurfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
- tx = org.mockito.kotlin.mock<SurfaceControl.Transaction>()
- doReturn(tx).`when`(tx).setLayer(anyOrNull(), anyOrNull())
- doReturn(tx).`when`(tx).setColorSpaceAgnostic(anyOrNull(), anyOrNull())
+ tx = getTransactionMock()
parentLeash = org.mockito.kotlin.mock<SurfaceControl>()
surfaceBuilder = SurfaceControl.Builder()
spyOn(surfaceBuilder)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
index 29ea09aee61d..2c06dfda7917 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTestUtils.kt
@@ -16,9 +16,36 @@
package com.android.wm.shell.compatui.letterbox
+import android.view.SurfaceControl
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.verification.VerificationMode
+/**
+ * @return A [SurfaceControl.Transaction] mock supporting chaining for some operations. Please
+ * add other operations if needed.
+ */
+fun getTransactionMock(): SurfaceControl.Transaction = mock<SurfaceControl.Transaction>().apply {
+ doReturn(this).`when`(this).setLayer(anyOrNull(), anyOrNull())
+ doReturn(this).`when`(this).setColorSpaceAgnostic(anyOrNull(), anyOrNull())
+ doReturn(this).`when`(this).setPosition(anyOrNull(), any(), any())
+ doReturn(this).`when`(this).setWindowCrop(anyOrNull(), any(), any())
+}
+
// Utility to make verification mode depending on a [Boolean].
fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
+
+// Utility matchers to use for the main types as Mockito [VerificationMode].
+object LetterboxMatchers {
+ fun Int.asAnyMode() = asAnyMode { this < 0 }
+ fun Float.asAnyMode() = asAnyMode { this < 0f }
+ fun String.asAnyMode() = asAnyMode { this.isEmpty() }
+}
+
+private inline fun <reified T : Any> T.asAnyMode(condition: () -> Boolean) =
+ (if (condition()) any() else eq(this))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
new file mode 100644
index 000000000000..295d4edf206b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [MultiSurfaceLetterboxController].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:MultiSurfaceLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MultiSurfaceLetterboxControllerTest : ShellTestCase() {
+
+ @Test
+ fun `When creation is requested the surfaces are created if not present`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked(times = 4)
+ }
+ }
+
+ @Test
+ fun `When creation is requested multiple times the surfaces are created once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked(times = 4)
+ }
+ }
+
+ @Test
+ fun `Different surfaces are created for every key`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+
+ r.checkSurfaceBuilderInvoked(times = 12)
+ }
+ }
+
+ @Test
+ fun `Created surface are removed once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.checkSurfaceBuilderInvoked(times = 4)
+
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+
+ r.checkTransactionRemovedInvoked(times = 4)
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive visibility update`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendUpdateSurfaceVisibilityRequest(visible = true)
+ r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20)
+
+ r.checkVisibilityUpdated(times = 4, expectedVisibility = true)
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive taskBounds update`() {
+ runTestScenario { r ->
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+
+ r.checkSurfacePositionUpdated(times = 0)
+ r.checkSurfaceSizeUpdated(times = 0)
+
+ r.sendCreateSurfaceRequest()
+
+ // Pillarbox.
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+ // The Left and Top surfaces.
+ r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f)
+ // The Right surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 1500f, expectedY = 0f)
+ // The Bottom surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1000f)
+ // Left and Right surface.
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 500, expectedHeight = 1000)
+ // Top and Button.
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 2000, expectedHeight = 0)
+
+ r.resetTransitionTest()
+
+ // Letterbox.
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 1000, 2000),
+ activityBounds = Rect(0, 500, 1000, 1500)
+ )
+ // Top and Left surfaces.
+ r.checkSurfacePositionUpdated(times = 2, expectedX = 0f, expectedY = 0f)
+ // Bottom surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 1500f)
+ // Right surface.
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 1000f, expectedY = 0f)
+
+ // Left and Right surfaces.
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 0, expectedHeight = 2000)
+ // Top and Button surfaces,
+ r.checkSurfaceSizeUpdated(times = 2, expectedWidth = 1000, expectedHeight = 500)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
+ val robot =
+ LetterboxControllerRobotTest(mContext, { sb -> MultiSurfaceLetterboxController(sb) })
+ consumer.accept(robot)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
new file mode 100644
index 000000000000..125e700bcd42
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [SingleSurfaceLetterboxController].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SingleSurfaceLetterboxControllerTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SingleSurfaceLetterboxControllerTest : ShellTestCase() {
+
+ @Test
+ fun `When creation is requested the surface is created if not present`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked()
+ }
+ }
+
+ @Test
+ fun `When creation is requested multiple times the surface is created once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+
+ r.checkSurfaceBuilderInvoked(times = 1)
+ }
+ }
+
+ @Test
+ fun `A different surface is created for every key`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest()
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2)
+ r.sendCreateSurfaceRequest(displayId = 2, taskId = 2)
+
+ r.checkSurfaceBuilderInvoked(times = 3)
+ }
+ }
+
+ @Test
+ fun `Created surface is removed once`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.checkSurfaceBuilderInvoked()
+
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+ r.sendDestroySurfaceRequest()
+
+ r.checkTransactionRemovedInvoked()
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive visibility update`() {
+ runTestScenario { r ->
+ r.sendCreateSurfaceRequest()
+ r.sendUpdateSurfaceVisibilityRequest(visible = true)
+ r.sendUpdateSurfaceVisibilityRequest(visible = true, displayId = 20)
+
+ r.checkVisibilityUpdated(expectedVisibility = true)
+ }
+ }
+
+ @Test
+ fun `Only existing surfaces receive taskBounds update`() {
+ runTestScenario { r ->
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+
+ r.checkSurfacePositionUpdated(times = 0)
+ r.checkSurfaceSizeUpdated(times = 0)
+
+ r.resetTransitionTest()
+
+ r.sendCreateSurfaceRequest()
+ r.sendUpdateSurfaceBoundsRequest(
+ taskBounds = Rect(0, 0, 2000, 1000),
+ activityBounds = Rect(500, 0, 1500, 1000)
+ )
+ r.checkSurfacePositionUpdated(times = 1, expectedX = 0f, expectedY = 0f)
+ r.checkSurfaceSizeUpdated(times = 1, expectedWidth = 2000, expectedHeight = 1000)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
+ val robot =
+ LetterboxControllerRobotTest(mContext, { sb -> SingleSurfaceLetterboxController(sb) })
+ consumer.accept(robot)
+ }
+}