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.statusbar.phone
18 
19 import android.graphics.Color
20 import android.graphics.Rect
21 import android.testing.AndroidTestingRunner
22 import android.view.WindowInsetsController
23 import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
24 import androidx.test.filters.SmallTest
25 import com.android.internal.statusbar.LetterboxDetails
26 import com.android.internal.view.AppearanceRegion
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.dump.DumpManager
29 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
30 import com.google.common.truth.Expect
31 import com.google.common.truth.Truth.assertThat
32 import org.junit.Before
33 import org.junit.Rule
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.mockito.Mock
37 import org.mockito.Mockito.`when` as whenever
38 import org.mockito.MockitoAnnotations
39 
40 @RunWith(AndroidTestingRunner::class)
41 @SmallTest
42 class LetterboxAppearanceCalculatorTest : SysuiTestCase() {
43 
44     companion object {
45         private const val DEFAULT_APPEARANCE = 0
46         private const val TEST_APPEARANCE = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
47         private val TEST_APPEARANCE_REGION_BOUNDS = Rect(0, 0, 20, 100)
48         private val TEST_APPEARANCE_REGION =
49             AppearanceRegion(TEST_APPEARANCE, TEST_APPEARANCE_REGION_BOUNDS)
50         private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
51         private val TEST_WINDOW_BOUNDS = Rect(0, 0, 500, 500)
52     }
53 
54     @get:Rule var expect = Expect.create()
55 
56     @Mock private lateinit var lightBarController: LightBarController
57     @Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
58     @Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
59     @Mock private lateinit var dumpManager: DumpManager
60     @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider
61 
62     private lateinit var calculator: LetterboxAppearanceCalculator
63 
64     @Before
65     fun setUp() {
66         MockitoAnnotations.initMocks(this)
67         whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
68         calculator =
69             LetterboxAppearanceCalculator(
70                 lightBarController, dumpManager, letterboxBackgroundProvider)
71         calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
72         whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK)
73         whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false)
74     }
75 
76     @Test
77     fun getLetterboxAppearance_overlapStartSide_returnsOriginalWithScrim() {
78         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
79         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
80         val letterbox = letterboxWithInnerBounds(Rect(50, 50, 150, 150))
81 
82         val letterboxAppearance =
83             calculator.getLetterboxAppearance(
84                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
85 
86         expect
87             .that(letterboxAppearance.appearance)
88             .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
89         expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
90     }
91 
92     @Test
93     fun getLetterboxAppearance_overlapEndSide_returnsOriginalWithScrim() {
94         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
95         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
96         val letterbox = letterboxWithInnerBounds(Rect(150, 50, 250, 150))
97 
98         val letterboxAppearance =
99             calculator.getLetterboxAppearance(
100                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
101 
102         expect
103             .that(letterboxAppearance.appearance)
104             .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
105         expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
106     }
107 
108     /** Regression test for b/287508741 */
109     @Test
110     fun getLetterboxAppearance_withOverlap_doesNotMutateOriginalBounds() {
111         val statusBarStartSideBounds = Rect(left = 0, top = 0, right = 100, bottom = 100)
112         val statusBarEndSideBounds = Rect(left = 200, top = 0, right = 300, bottom = 100)
113         val letterBoxInnerBounds = Rect(left = 150, top = 50, right = 250, bottom = 150)
114         val statusBarStartSideBoundsCopy = Rect(statusBarStartSideBounds)
115         val statusBarEndSideBoundsCopy = Rect(statusBarEndSideBounds)
116         val letterBoxInnerBoundsCopy = Rect(letterBoxInnerBounds)
117         whenever(statusBarBoundsProvider.visibleStartSideBounds)
118                 .thenReturn(statusBarStartSideBounds)
119         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(statusBarEndSideBounds)
120 
121         calculator.getLetterboxAppearance(
122                 TEST_APPEARANCE,
123                 TEST_APPEARANCE_REGIONS,
124                 arrayOf(letterboxWithInnerBounds(letterBoxInnerBounds))
125         )
126 
127         expect.that(statusBarStartSideBounds).isEqualTo(statusBarStartSideBoundsCopy)
128         expect.that(statusBarEndSideBounds).isEqualTo(statusBarEndSideBoundsCopy)
129         expect.that(letterBoxInnerBounds).isEqualTo(letterBoxInnerBoundsCopy)
130     }
131 
132     @Test
133     fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() {
134         whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true)
135         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
136         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
137         val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
138 
139         val letterboxAppearance =
140             calculator.getLetterboxAppearance(
141                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
142 
143         expect
144                 .that(letterboxAppearance.appearance)
145                 .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
146         expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
147     }
148 
149     @Test
150     fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
151         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
152         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
153         val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
154 
155         val letterboxAppearance =
156             calculator.getLetterboxAppearance(
157                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
158 
159         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
160     }
161 
162     @Test
163     fun getLetterboxAppearance_letterboxContainsStartSide_returnsAppearanceWithoutScrim() {
164         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
165         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
166         val letterbox = letterboxWithInnerBounds(Rect(0, 0, 101, 101))
167 
168         val letterboxAppearance =
169             calculator.getLetterboxAppearance(
170                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
171 
172         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
173     }
174 
175     @Test
176     fun getLetterboxAppearance_letterboxContainsEndSide_returnsAppearanceWithoutScrim() {
177         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
178         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
179         val letterbox = letterboxWithInnerBounds(Rect(199, 0, 301, 101))
180 
181         val letterboxAppearance =
182             calculator.getLetterboxAppearance(
183                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
184 
185         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
186     }
187 
188     @Test
189     fun getLetterboxAppearance_letterboxContainsEntireStatusBar_returnsAppearanceWithoutScrim() {
190         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
191         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
192         val letterbox = letterboxWithInnerBounds(Rect(0, 0, 300, 100))
193 
194         val letterboxAppearance =
195             calculator.getLetterboxAppearance(
196                 TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
197 
198         assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
199     }
200 
201     @Test
202     fun getLetterboxAppearance_returnsAdaptedAppearanceRegions_basedOnLetterboxInnerBounds() {
203         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
204         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
205         val letterbox = letterboxWithInnerBounds(Rect(150, 0, 300, 800))
206         val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds)
207 
208         val letterboxAppearance =
209             calculator.getLetterboxAppearance(
210                 TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
211 
212         val letterboxAdaptedRegion = letterboxRegion.copy(bounds = letterbox.letterboxInnerBounds)
213         assertThat(letterboxAppearance.appearanceRegions.toList()).contains(letterboxAdaptedRegion)
214         assertThat(letterboxAppearance.appearanceRegions.toList()).doesNotContain(letterboxRegion)
215     }
216 
217     @Test
218     fun getLetterboxAppearance_returnsDefaultAppearanceRegions_basedOnLetterboxOuterBounds() {
219         whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
220         whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
221         val letterbox =
222             letterboxWithBounds(
223                 innerBounds = Rect(left = 25, top = 0, right = 75, bottom = 100),
224                 fullBounds = Rect(left = 0, top = 0, right = 100, bottom = 100))
225         val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds)
226 
227         val letterboxAppearance =
228             calculator.getLetterboxAppearance(
229                 TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
230 
231         val outerRegions =
232             listOf(
233                 AppearanceRegion(
234                     DEFAULT_APPEARANCE, Rect(left = 0, top = 0, right = 25, bottom = 100)),
235                 AppearanceRegion(
236                     DEFAULT_APPEARANCE, Rect(left = 75, top = 0, right = 100, bottom = 100)),
237             )
238         assertThat(letterboxAppearance.appearanceRegions.toList())
239             .containsAtLeastElementsIn(outerRegions)
240     }
241 
242     private fun letterboxWithBounds(innerBounds: Rect, fullBounds: Rect) =
243         LetterboxDetails(innerBounds, fullBounds, TEST_APPEARANCE)
244 
245     private fun letterboxWithInnerBounds(innerBounds: Rect) =
246         letterboxWithBounds(innerBounds, fullBounds = TEST_WINDOW_BOUNDS)
247 }
248 
249 private fun AppearanceRegion.copy(appearance: Int = this.appearance, bounds: Rect = this.bounds) =
250     AppearanceRegion(appearance, bounds)
251 
252 private fun Rect(left: Int, top: Int, right: Int, bottom: Int) = Rect(left, top, right, bottom)
253