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
18 
19 import android.content.Context
20 import android.graphics.Canvas
21 import android.graphics.Insets
22 import android.graphics.Path
23 import android.graphics.Rect
24 import android.graphics.RectF
25 import android.graphics.Region
26 import android.testing.AndroidTestingRunner
27 import android.view.Display
28 import android.view.DisplayCutout
29 import android.view.DisplayInfo
30 import android.view.View
31 import androidx.test.filters.SmallTest
32 import com.android.dx.mockito.inline.extended.ExtendedMockito.never
33 import com.android.internal.R
34 import com.android.systemui.util.mockito.eq
35 import com.google.common.truth.Truth.assertThat
36 import org.junit.Before
37 import org.junit.Test
38 import org.junit.runner.RunWith
39 import org.mockito.Mock
40 import org.mockito.Mockito.spy
41 import org.mockito.Mockito.verify
42 import org.mockito.MockitoAnnotations
43 import org.mockito.Mockito.`when` as whenever
44 
45 @RunWith(AndroidTestingRunner::class)
46 @SmallTest
47 class DisplayCutoutBaseViewTest : SysuiTestCase() {
48 
49     @Mock private lateinit var mockCanvas: Canvas
50     @Mock private lateinit var mockRootView: View
51     @Mock private lateinit var mockDisplay: Display
52     @Mock private lateinit var mockContext: Context
53 
54     private lateinit var cutoutBaseView: DisplayCutoutBaseView
55     private val cutout: DisplayCutout = DisplayCutout.Builder()
56             .setSafeInsets(Insets.of(0, 2, 0, 0))
57             .setBoundingRectTop(Rect(1, 0, 2, 2))
58             .build()
59 
60     @Before
setUpnull61     fun setUp() {
62         MockitoAnnotations.initMocks(this)
63     }
64 
65     @Test
testBoundingRectsToRegionnull66     fun testBoundingRectsToRegion() {
67         setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
68         val rect = Rect(1, 2, 3, 4)
69         assertThat(cutoutBaseView.rectsToRegion(listOf(rect)).bounds).isEqualTo(rect)
70     }
71 
72     @Test
testDrawCutout_fillCutoutnull73     fun testDrawCutout_fillCutout() {
74         setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
75         cutoutBaseView.onDraw(mockCanvas)
76 
77         verify(cutoutBaseView).drawCutouts(mockCanvas)
78     }
79 
80     @Test
testDrawCutout_notFillCutoutnull81     fun testDrawCutout_notFillCutout() {
82         setupDisplayCutoutBaseView(false /* fillCutout */, true /* hasCutout */)
83         cutoutBaseView.onDraw(mockCanvas)
84 
85         verify(cutoutBaseView, never()).drawCutouts(mockCanvas)
86     }
87 
88     @Test
testShouldInterceptTouch_hasCutoutnull89     fun testShouldInterceptTouch_hasCutout() {
90         setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
91         cutoutBaseView.updateCutout()
92 
93         assertThat(cutoutBaseView.shouldInterceptTouch()).isTrue()
94     }
95 
96     @Test
testShouldInterceptTouch_noCutoutnull97     fun testShouldInterceptTouch_noCutout() {
98         setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
99         cutoutBaseView.updateCutout()
100 
101         assertThat(cutoutBaseView.shouldInterceptTouch()).isFalse()
102     }
103 
104     @Test
testGetInterceptRegion_hasCutoutnull105     fun testGetInterceptRegion_hasCutout() {
106         setupDisplayCutoutBaseView(true /* fillCutout */, true /* hasCutout */)
107         whenever(mockRootView.left).thenReturn(0)
108         whenever(mockRootView.top).thenReturn(0)
109         whenever(mockRootView.right).thenReturn(100)
110         whenever(mockRootView.bottom).thenReturn(200)
111 
112         val expect = Region()
113         expect.op(cutout.boundingRectTop, Region.Op.UNION)
114         expect.op(0, 0, 100, 200, Region.Op.INTERSECT)
115 
116         cutoutBaseView.updateCutout()
117 
118         assertThat(cutoutBaseView.interceptRegion).isEqualTo(expect)
119     }
120 
121     @Test
testGetInterceptRegion_noCutoutnull122     fun testGetInterceptRegion_noCutout() {
123         setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
124         cutoutBaseView.updateCutout()
125 
126         assertThat(cutoutBaseView.interceptRegion).isNull()
127     }
128 
129     @Test
testCutoutProtectionnull130     fun testCutoutProtection() {
131         setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
132         val bounds = Rect(0, 0, 10, 10)
133         val path = Path()
134         val pathBounds = RectF(bounds)
135         path.addRect(pathBounds, Path.Direction.CCW)
136 
137         context.mainExecutor.execute {
138             cutoutBaseView.setProtection(path, bounds)
139             cutoutBaseView.enableShowProtection(true)
140         }
141         waitForIdleSync()
142 
143         assertThat(cutoutBaseView.protectionPath.isRect(pathBounds)).isTrue()
144         assertThat(cutoutBaseView.protectionRect).isEqualTo(pathBounds)
145     }
146 
147     @Test
testCutoutProtection_withDisplayRationull148     fun testCutoutProtection_withDisplayRatio() {
149         setupDisplayCutoutBaseView(true /* fillCutout */, false /* hasCutout */)
150         whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(0.5f)
151         val bounds = Rect(0, 0, 10, 10)
152         val path = Path()
153         val pathBounds = RectF(bounds)
154         path.addRect(pathBounds, Path.Direction.CCW)
155 
156         context.mainExecutor.execute {
157             cutoutBaseView.setProtection(path, bounds)
158             cutoutBaseView.enableShowProtection(true)
159         }
160         waitForIdleSync()
161 
162         assertThat(cutoutBaseView.protectionPath.isRect(pathBounds)).isTrue()
163         assertThat(cutoutBaseView.protectionRect).isEqualTo(RectF(0f, 0f, 5f, 5f))
164     }
165 
setupDisplayCutoutBaseViewnull166     private fun setupDisplayCutoutBaseView(fillCutout: Boolean, hasCutout: Boolean) {
167         mContext.orCreateTestableResources.addOverride(
168                 R.array.config_displayUniqueIdArray, arrayOf<String>())
169         mContext.orCreateTestableResources.addOverride(
170                 R.bool.config_fillMainBuiltInDisplayCutout, fillCutout)
171 
172         cutoutBaseView = spy(DisplayCutoutBaseView(mContext))
173 
174         whenever(cutoutBaseView.context).thenReturn(mockContext)
175         whenever(mockContext.display).thenReturn(mockDisplay)
176         whenever(mockDisplay.uniqueId).thenReturn("mockDisplayUniqueId")
177         whenever(cutoutBaseView.rootView).thenReturn(mockRootView)
178         whenever(cutoutBaseView.getPhysicalPixelDisplaySizeRatio()).thenReturn(1f)
179         whenever(mockDisplay.getDisplayInfo(eq(cutoutBaseView.displayInfo))
180         ).then {
181             val info = it.getArgument<DisplayInfo>(0)
182             info.displayCutout = if (hasCutout) cutout else null
183             return@then true
184         }
185     }
186 }