xref: /aosp_15_r20/frameworks/base/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui
18 
19 import android.graphics.Rect
20 import android.view.Display
21 import android.view.DisplayAdjustments
22 import android.view.DisplayCutout
23 import android.view.DisplayInfo
24 import android.view.Surface
25 import android.view.Surface.Rotation
26 import androidx.test.ext.junit.runners.AndroidJUnit4
27 import androidx.test.filters.SmallTest
28 import com.android.systemui.util.mockito.any
29 import com.android.systemui.util.mockito.mock
30 import com.android.systemui.util.mockito.whenever
31 import com.google.common.truth.Truth.assertThat
32 import org.junit.Test
33 import org.junit.runner.RunWith
34 
35 @SmallTest
36 @RunWith(AndroidJUnit4::class)
37 class SysUICutoutProviderTest : SysuiTestCase() {
38 
39     private val fakeProtectionLoader = FakeCameraProtectionLoader(context)
40 
41     @Test
cutoutInfoForCurrentDisplay_noCutout_returnsNullnull42     fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() {
43         val noCutoutDisplay = createDisplay(cutout = null)
44         val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
45         val provider = SysUICutoutProviderImpl(noCutoutDisplayContext, fakeProtectionLoader)
46 
47         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()
48 
49         assertThat(sysUICutout).isNull()
50     }
51 
52     @Test
cutoutInfoForCurrentDisplay_returnsCutoutnull53     fun cutoutInfoForCurrentDisplay_returnsCutout() {
54         val cutoutDisplay = createDisplay()
55         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
56         val provider = SysUICutoutProviderImpl(cutoutDisplayContext, fakeProtectionLoader)
57 
58         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
59 
60         assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
61     }
62 
63     @Test
cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtectionnull64     fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() {
65         val cutoutDisplay = createDisplay()
66         val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
67         val provider = SysUICutoutProviderImpl(cutoutDisplayContext, fakeProtectionLoader)
68 
69         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
70 
71         assertThat(sysUICutout.cameraProtection).isNull()
72     }
73 
74     @Test
cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtectionnull75     fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() {
76         fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID)
77         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
78         val provider = SysUICutoutProviderImpl(outerDisplayContext, fakeProtectionLoader)
79 
80         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
81 
82         assertThat(sysUICutout.cameraProtection).isNotNull()
83     }
84 
85     @Test
cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtectionnull86     fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() {
87         fakeProtectionLoader.clearProtectionInfoList()
88         val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
89         val provider = SysUICutoutProviderImpl(outerDisplayContext, fakeProtectionLoader)
90 
91         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
92 
93         assertThat(sysUICutout.cameraProtection).isNull()
94     }
95 
96     @Test
cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtectionnull97     fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() {
98         fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
99         val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
100         val provider = SysUICutoutProviderImpl(displayContext, fakeProtectionLoader)
101 
102         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
103 
104         assertThat(sysUICutout.cameraProtection).isNull()
105     }
106 
107     @Test
cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtectionnull108     fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() {
109         fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
110         val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
111         val provider = SysUICutoutProviderImpl(displayContext, fakeProtectionLoader)
112 
113         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
114 
115         assertThat(sysUICutout.cameraProtection).isNull()
116     }
117 
118     @Test
cutoutInfo_rotation0_returnsOriginalProtectionBoundsnull119     fun cutoutInfo_rotation0_returnsOriginalProtectionBounds() {
120         val provider =
121             setUpProviderWithCameraProtection(
122                 displayWidth = 500,
123                 displayHeight = 1000,
124                 rotation = Surface.ROTATION_0,
125                 protectionBounds =
126                     Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110),
127             )
128 
129         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
130 
131         assertThat(sysUICutout.cameraProtection!!.bounds)
132             .isEqualTo(Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110))
133     }
134 
135     @Test
cutoutInfo_rotation90_returnsRotatedProtectionBoundsnull136     fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() {
137         val provider =
138             setUpProviderWithCameraProtection(
139                 displayWidth = 500,
140                 displayHeight = 1000,
141                 rotation = Surface.ROTATION_90,
142                 protectionBounds =
143                     Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110),
144             )
145 
146         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
147 
148         assertThat(sysUICutout.cameraProtection!!.bounds)
149             .isEqualTo(Rect(/* left= */ 10, /* top= */ 10, /* right= */ 110, /* bottom= */ 60))
150     }
151 
152     @Test
cutoutInfo_withRotation_doesNotMutateOriginalBoundsnull153     fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() {
154         val displayNaturalWidth = 500
155         val displayNaturalHeight = 1000
156         val originalProtectionBounds =
157             Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110)
158         // Safe copy as we don't know at which layer the mutation could happen
159         val originalProtectionBoundsCopy = Rect(originalProtectionBounds)
160         val display =
161             createDisplay(
162                 uniqueId = OUTER_DISPLAY_UNIQUE_ID,
163                 rotation = Surface.ROTATION_180,
164                 width = displayNaturalWidth,
165                 height = displayNaturalHeight,
166             )
167         fakeProtectionLoader.addOuterCameraProtection(
168             displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
169             bounds = originalProtectionBounds,
170         )
171         val provider =
172             SysUICutoutProviderImpl(context.createDisplayContext(display), fakeProtectionLoader)
173 
174         // Here we get the rotated bounds once
175         provider.cutoutInfoForCurrentDisplayAndRotation()
176 
177         // Rotate display back to original rotation
178         whenever(display.rotation).thenReturn(Surface.ROTATION_0)
179 
180         // Now the bounds should match the original ones. We are checking for mutation since Rect
181         // is mutable and has many methods that mutate the instance, and it is easy to do it by
182         // mistake.
183         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
184         assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy)
185     }
186 
187     @Test
cutoutInfo_rotation180_returnsRotatedProtectionBoundsnull188     fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() {
189         val provider =
190             setUpProviderWithCameraProtection(
191                 displayWidth = 500,
192                 displayHeight = 1000,
193                 rotation = Surface.ROTATION_180,
194                 protectionBounds =
195                     Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110),
196             )
197 
198         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
199 
200         assertThat(sysUICutout.cameraProtection!!.bounds)
201             .isEqualTo(Rect(/* left= */ 10, /* top= */ 890, /* right= */ 60, /* bottom= */ 990))
202     }
203 
204     @Test
cutoutInfo_rotation270_returnsRotatedProtectionBoundsnull205     fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() {
206         val provider =
207             setUpProviderWithCameraProtection(
208                 displayWidth = 500,
209                 displayHeight = 1000,
210                 rotation = Surface.ROTATION_270,
211                 protectionBounds =
212                     Rect(/* left= */ 440, /* top= */ 10, /* right= */ 490, /* bottom= */ 110),
213             )
214 
215         val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
216 
217         assertThat(sysUICutout.cameraProtection!!.bounds)
218             .isEqualTo(Rect(/* left= */ 890, /* top= */ 440, /* right= */ 990, /* bottom= */ 490))
219     }
220 
setUpProviderWithCameraProtectionnull221     private fun setUpProviderWithCameraProtection(
222         displayWidth: Int,
223         displayHeight: Int,
224         rotation: Int = Surface.ROTATION_0,
225         protectionBounds: Rect,
226     ): SysUICutoutProvider {
227         val display =
228             createDisplay(
229                 uniqueId = OUTER_DISPLAY_UNIQUE_ID,
230                 rotation = rotation,
231                 width =
232                     if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
233                         displayWidth
234                     } else {
235                         displayHeight
236                     },
237                 height =
238                     if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
239                         displayHeight
240                     else displayWidth,
241             )
242         fakeProtectionLoader.addOuterCameraProtection(
243             displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
244             bounds = protectionBounds,
245         )
246         return SysUICutoutProviderImpl(context.createDisplayContext(display), fakeProtectionLoader)
247     }
248 
249     companion object {
250         private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
251         private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
252 
createDisplaynull253         private fun createDisplay(
254             width: Int = 500,
255             height: Int = 1000,
256             @Rotation rotation: Int = Surface.ROTATION_0,
257             uniqueId: String? = "uniqueId",
258             cutout: DisplayCutout? = mock<DisplayCutout>(),
259         ) =
260             mock<Display> {
261                 whenever(this.getDisplayInfo(any())).thenAnswer {
262                     val displayInfo = it.arguments[0] as DisplayInfo
263                     displayInfo.rotation = rotation
264                     displayInfo.logicalWidth = width
265                     displayInfo.logicalHeight = height
266                     return@thenAnswer true
267                 }
268                 // Setting width and height to smaller values to simulate real behavior of this API
269                 // not always returning the real display size
270                 whenever(this.width).thenReturn(width - 5)
271                 whenever(this.height).thenReturn(height - 10)
272                 whenever(this.rotation).thenReturn(rotation)
273                 whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
274                 whenever(this.cutout).thenReturn(cutout)
275                 whenever(this.uniqueId).thenReturn(uniqueId)
276             }
277     }
278 }
279