1 /*
2  * Copyright (C) 2022 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.decor
18 
19 import android.graphics.Insets
20 import android.graphics.Rect
21 import android.testing.AndroidTestingRunner
22 import android.testing.TestableResources
23 import android.util.RotationUtils
24 import android.util.Size
25 import android.view.Display
26 import android.view.DisplayCutout
27 import android.view.DisplayCutout.BOUNDS_POSITION_LENGTH
28 import android.view.DisplayInfo
29 import android.view.Surface
30 import androidx.test.filters.SmallTest
31 import com.android.systemui.SysuiTestCase
32 import com.android.systemui.util.mockito.any
33 import org.junit.Assert
34 import org.junit.Before
35 import org.junit.Test
36 import org.junit.runner.RunWith
37 import org.mockito.Mock
38 import org.mockito.Mockito.doAnswer
39 import org.mockito.MockitoAnnotations
40 
41 @RunWith(AndroidTestingRunner::class)
42 @SmallTest
43 class CutoutDecorProviderFactoryTest : SysuiTestCase() {
44 
45     @Mock private lateinit var display: Display
46     private var testableRes: TestableResources? = null
47     private lateinit var factory: CutoutDecorProviderFactory
48 
49     @Before
50     fun setUp() {
51         MockitoAnnotations.initMocks(this)
52         testableRes = mContext.orCreateTestableResources
53         factory = CutoutDecorProviderFactory(testableRes!!.resources, display)
54     }
55 
56     private fun setupFillCutout(fillCutout: Boolean) {
57         testableRes!!.addOverride(
58             com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout
59         )
60     }
61 
62     private fun setupDisplayInfo(
63         displayCutout: DisplayCutout? = null,
64         @Surface.Rotation rotation: Int = Surface.ROTATION_0,
65         displayId: Int = -1
66     ) {
67         doAnswer {
68             it.getArgument<DisplayInfo>(0).let { info ->
69                 info.displayCutout = displayCutout
70                 info.rotation = rotation
71                 info.displayId = displayId
72             }
73             true
74         }.`when`(display).getDisplayInfo(any<DisplayInfo>())
75     }
76 
77     private fun getCutout(
78         safeInsets: Insets,
79         cutoutBounds: Array<Rect?>,
80         @Surface.Rotation rotation: Int = Surface.ROTATION_0,
81         cutoutParentSizeForRotate: Size = Size(100, 200)
82     ): DisplayCutout {
83         val insets = RotationUtils.rotateInsets(safeInsets, rotation)
84         val sorted = arrayOfNulls<Rect>(BOUNDS_POSITION_LENGTH)
85         for (pos in 0 until BOUNDS_POSITION_LENGTH) {
86             val rotatedPos = (pos - rotation + BOUNDS_POSITION_LENGTH) % BOUNDS_POSITION_LENGTH
87             if (cutoutBounds[pos] != null) {
88                 RotationUtils.rotateBounds(
89                     cutoutBounds[pos],
90                     cutoutParentSizeForRotate.width,
91                     cutoutParentSizeForRotate.height,
92                     rotation
93                 )
94             }
95             sorted[rotatedPos] = cutoutBounds[pos]
96         }
97         return DisplayCutout(
98             insets,
99             sorted[DisplayCutout.BOUNDS_POSITION_LEFT],
100             sorted[DisplayCutout.BOUNDS_POSITION_TOP],
101             sorted[DisplayCutout.BOUNDS_POSITION_RIGHT],
102             sorted[DisplayCutout.BOUNDS_POSITION_BOTTOM]
103         )
104     }
105 
106     @Test
107     fun testGetNothingIfNoCutout() {
108         setupFillCutout(false)
109 
110         Assert.assertFalse(factory.hasProviders)
111         Assert.assertEquals(0, factory.providers.size)
112     }
113 
114     @Test
115     fun testGetTopCutoutProvider() {
116         setupFillCutout(true)
117         setupDisplayInfo(
118             getCutout(
119                 safeInsets = Insets.of(0, 1, 0, 0),
120                 cutoutBounds = arrayOf(null, Rect(9, 0, 10, 1), null, null)
121             )
122         )
123 
124         Assert.assertTrue(factory.hasProviders)
125 
126         val providers = factory.providers
127         Assert.assertEquals(1, providers.size)
128         Assert.assertEquals(1, providers[0].numOfAlignedBound)
129         Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_TOP, providers[0].alignedBounds[0])
130     }
131 
132     @Test
133     fun testGetBottomCutoutProviderOnLandscape() {
134         setupFillCutout(true)
135         setupDisplayInfo(
136             getCutout(
137                 safeInsets = Insets.of(0, 0, 0, 1),
138                 cutoutBounds = arrayOf(null, null, null, Rect(45, 199, 55, 200)),
139                 rotation = Surface.ROTATION_90
140             ),
141             Surface.ROTATION_90
142         )
143 
144         Assert.assertTrue(factory.hasProviders)
145 
146         val providers = factory.providers
147         Assert.assertEquals(1, providers.size)
148         Assert.assertEquals(1, providers[0].numOfAlignedBound)
149         Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_BOTTOM, providers[0].alignedBounds[0])
150     }
151 
152     @Test
153     fun testGetLeftCutoutProviderOnSeascape() {
154         setupFillCutout(true)
155         setupDisplayInfo(
156             getCutout(
157                 safeInsets = Insets.of(1, 0, 0, 0),
158                 cutoutBounds = arrayOf(Rect(0, 20, 1, 40), null, null, null),
159                 rotation = Surface.ROTATION_270
160             ),
161             Surface.ROTATION_270
162         )
163 
164         Assert.assertTrue(factory.hasProviders)
165 
166         val providers = factory.providers
167         Assert.assertEquals(1, providers.size)
168         Assert.assertEquals(1, providers[0].numOfAlignedBound)
169         Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_LEFT, providers[0].alignedBounds[0])
170     }
171 
172     @Test
173     fun testGetTopRightCutoutProviderOnReverse() {
174         setupFillCutout(true)
175         setupDisplayInfo(
176             getCutout(
177                 safeInsets = Insets.of(0, 1, 1, 0),
178                 cutoutBounds = arrayOf(
179                     null,
180                     Rect(9, 0, 10, 1),
181                     Rect(99, 40, 100, 60),
182                     null
183                 ),
184                 rotation = Surface.ROTATION_180
185             ),
186             Surface.ROTATION_180
187         )
188 
189         Assert.assertTrue(factory.hasProviders)
190 
191         val providers = factory.providers
192         Assert.assertEquals(2, providers.size)
193         Assert.assertEquals(1, providers[0].numOfAlignedBound)
194         Assert.assertEquals(1, providers[1].numOfAlignedBound)
195         providers.sortedBy { it.alignedBounds[0] }.let {
196             Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_TOP, it[0].alignedBounds[0])
197             Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_RIGHT, it[1].alignedBounds[0])
198         }
199     }
200 }
201