1 /*
2  * Copyright (C) 2021 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.content.res.Configuration
20 import android.graphics.Insets
21 import android.graphics.Rect
22 import android.testing.TestableLooper.RunWithLooper
23 import android.view.DisplayCutout
24 import android.view.DisplayShape
25 import android.view.LayoutInflater
26 import android.view.MotionEvent
27 import android.view.PrivacyIndicatorBounds
28 import android.view.RoundedCorners
29 import android.view.View
30 import android.view.WindowInsets
31 import android.widget.FrameLayout
32 import androidx.test.filters.SmallTest
33 import com.android.systemui.Flags
34 import com.android.systemui.Gefingerpoken
35 import com.android.systemui.SysuiTestCase
36 import com.android.systemui.plugins.DarkIconDispatcher
37 import com.android.systemui.res.R
38 import com.android.systemui.statusbar.window.StatusBarWindowController
39 import com.android.systemui.util.mockito.mock
40 import com.android.systemui.util.mockito.whenever
41 import com.google.common.truth.Truth.assertThat
42 import org.junit.Before
43 import org.junit.Test
44 import org.mockito.Mockito.never
45 import org.mockito.Mockito.spy
46 import org.mockito.Mockito.times
47 import org.mockito.Mockito.verify
48 
49 @SmallTest
50 @RunWithLooper(setAsMainLooper = true)
51 class PhoneStatusBarViewTest : SysuiTestCase() {
52 
53     private lateinit var view: PhoneStatusBarView
54     private val systemIconsContainer: View
55         get() = view.requireViewById(R.id.system_icons)
56 
57     private val contentInsetsProvider = mock<StatusBarContentInsetsProvider>()
58     private val windowController = mock<StatusBarWindowController>()
59 
60     @Before
setUpnull61     fun setUp() {
62         mDependency.injectTestDependency(
63             StatusBarContentInsetsProvider::class.java,
64             contentInsetsProvider
65         )
66         mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>())
67         mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
68         context.ensureTestableResources()
69         view = spy(createStatusBarView())
70         whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
71         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
72             .thenReturn(Insets.NONE)
73     }
74 
75     @Test
onTouchEvent_listenerNotifiednull76     fun onTouchEvent_listenerNotified() {
77         val handler = TestTouchEventHandler()
78         view.setTouchEventHandler(handler)
79 
80         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
81         view.onTouchEvent(event)
82 
83         assertThat(handler.lastEvent).isEqualTo(event)
84     }
85 
86     @Test
onInterceptTouchEvent_listenerNotifiednull87     fun onInterceptTouchEvent_listenerNotified() {
88         val handler = TestTouchEventHandler()
89         view.setTouchEventHandler(handler)
90 
91         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
92         view.onInterceptTouchEvent(event)
93 
94         assertThat(handler.lastInterceptEvent).isEqualTo(event)
95     }
96 
97     @Test
onTouchEvent_listenerReturnsTrue_viewReturnsTruenull98     fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
99         val handler = TestTouchEventHandler()
100         view.setTouchEventHandler(handler)
101         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
102 
103         handler.handleTouchReturnValue = true
104 
105         assertThat(view.onTouchEvent(event)).isTrue()
106     }
107 
108     @Test
onTouchEvent_listenerReturnsFalse_viewReturnsFalsenull109     fun onTouchEvent_listenerReturnsFalse_viewReturnsFalse() {
110         val handler = TestTouchEventHandler()
111         view.setTouchEventHandler(handler)
112         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
113 
114         handler.handleTouchReturnValue = false
115 
116         assertThat(view.onTouchEvent(event)).isFalse()
117     }
118 
119     @Test
onTouchEvent_noListener_noCrashnull120     fun onTouchEvent_noListener_noCrash() {
121         view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
122         // No assert needed, just testing no crash
123     }
124 
125     @Test
onAttachedToWindow_flagEnabled_updatesWindowHeightnull126     fun onAttachedToWindow_flagEnabled_updatesWindowHeight() {
127         mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
128 
129         view.onAttachedToWindow()
130 
131         verify(windowController).refreshStatusBarHeight()
132     }
133 
134     @Test
onAttachedToWindow_flagDisabled_doesNotUpdateWindowHeightnull135     fun onAttachedToWindow_flagDisabled_doesNotUpdateWindowHeight() {
136         mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
137 
138         view.onAttachedToWindow()
139 
140         verify(windowController, never()).refreshStatusBarHeight()
141     }
142 
143     @Test
onConfigurationChanged_flagEnabled_updatesWindowHeightnull144     fun onConfigurationChanged_flagEnabled_updatesWindowHeight() {
145         mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
146 
147         view.onConfigurationChanged(Configuration())
148 
149         verify(windowController).refreshStatusBarHeight()
150     }
151 
152     @Test
onConfigurationChanged_multipleCalls_flagEnabled_updatesWindowHeightMultipleTimesnull153     fun onConfigurationChanged_multipleCalls_flagEnabled_updatesWindowHeightMultipleTimes() {
154         mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
155 
156         view.onConfigurationChanged(Configuration())
157         view.onConfigurationChanged(Configuration())
158         view.onConfigurationChanged(Configuration())
159         view.onConfigurationChanged(Configuration())
160 
161         verify(windowController, times(4)).refreshStatusBarHeight()
162     }
163 
164     @Test
onConfigurationChanged_flagDisabled_doesNotUpdateWindowHeightnull165     fun onConfigurationChanged_flagDisabled_doesNotUpdateWindowHeight() {
166         mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
167 
168         view.onConfigurationChanged(Configuration())
169 
170         verify(windowController, never()).refreshStatusBarHeight()
171     }
172 
173     @Test
onConfigurationChanged_multipleCalls_flagDisabled_doesNotUpdateWindowHeightnull174     fun onConfigurationChanged_multipleCalls_flagDisabled_doesNotUpdateWindowHeight() {
175         mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
176 
177         view.onConfigurationChanged(Configuration())
178         view.onConfigurationChanged(Configuration())
179         view.onConfigurationChanged(Configuration())
180         view.onConfigurationChanged(Configuration())
181 
182         verify(windowController, never()).refreshStatusBarHeight()
183     }
184 
185     @Test
onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsetsnull186     fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
187         val insets = Insets.of(/* left = */ 10, /* top = */ 20, /* right = */ 30, /* bottom = */ 40)
188         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
189             .thenReturn(insets)
190 
191         view.onAttachedToWindow()
192 
193         assertThat(view.paddingLeft).isEqualTo(insets.left)
194         assertThat(view.paddingTop).isEqualTo(insets.top)
195         assertThat(view.paddingRight).isEqualTo(insets.right)
196         assertThat(view.paddingBottom).isEqualTo(0)
197     }
198 
199     @Test
onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsetsnull200     fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
201         val insets = Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
202         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
203             .thenReturn(insets)
204 
205         view.onConfigurationChanged(Configuration())
206 
207         assertThat(view.paddingLeft).isEqualTo(insets.left)
208         assertThat(view.paddingTop).isEqualTo(insets.top)
209         assertThat(view.paddingRight).isEqualTo(insets.right)
210         assertThat(view.paddingBottom).isEqualTo(0)
211     }
212 
213     @Test
onConfigurationChanged_noRelevantChange_doesNotUpdateInsetsnull214     fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() {
215         val previousInsets =
216             Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
217         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
218             .thenReturn(previousInsets)
219         context.orCreateTestableResources.overrideConfiguration(Configuration())
220         view.onAttachedToWindow()
221 
222         val newInsets = Insets.NONE
223         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
224             .thenReturn(newInsets)
225         view.onConfigurationChanged(Configuration())
226 
227         assertThat(view.paddingLeft).isEqualTo(previousInsets.left)
228         assertThat(view.paddingTop).isEqualTo(previousInsets.top)
229         assertThat(view.paddingRight).isEqualTo(previousInsets.right)
230         assertThat(view.paddingBottom).isEqualTo(0)
231     }
232 
233     @Test
onConfigurationChanged_densityChanged_updatesInsetsnull234     fun onConfigurationChanged_densityChanged_updatesInsets() {
235         val previousInsets =
236             Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
237         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
238             .thenReturn(previousInsets)
239         val configuration = Configuration()
240         configuration.densityDpi = 123
241         context.orCreateTestableResources.overrideConfiguration(configuration)
242         view.onAttachedToWindow()
243 
244         val newInsets = Insets.NONE
245         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
246             .thenReturn(newInsets)
247         configuration.densityDpi = 456
248         view.onConfigurationChanged(configuration)
249 
250         assertThat(view.paddingLeft).isEqualTo(newInsets.left)
251         assertThat(view.paddingTop).isEqualTo(newInsets.top)
252         assertThat(view.paddingRight).isEqualTo(newInsets.right)
253         assertThat(view.paddingBottom).isEqualTo(0)
254     }
255 
256     @Test
onConfigurationChanged_fontScaleChanged_updatesInsetsnull257     fun onConfigurationChanged_fontScaleChanged_updatesInsets() {
258         val previousInsets =
259             Insets.of(/* left = */ 40, /* top = */ 30, /* right = */ 20, /* bottom = */ 10)
260         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
261             .thenReturn(previousInsets)
262         val configuration = Configuration()
263         configuration.fontScale = 1f
264         context.orCreateTestableResources.overrideConfiguration(configuration)
265         view.onAttachedToWindow()
266 
267         val newInsets = Insets.NONE
268         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
269             .thenReturn(newInsets)
270         configuration.fontScale = 2f
271         view.onConfigurationChanged(configuration)
272 
273         assertThat(view.paddingLeft).isEqualTo(newInsets.left)
274         assertThat(view.paddingTop).isEqualTo(newInsets.top)
275         assertThat(view.paddingRight).isEqualTo(newInsets.right)
276         assertThat(view.paddingBottom).isEqualTo(0)
277     }
278 
279     @Test
onConfigurationChanged_systemIconsHeightChanged_containerHeightIsUpdatednull280     fun onConfigurationChanged_systemIconsHeightChanged_containerHeightIsUpdated() {
281         val newHeight = 123456
282         context.orCreateTestableResources.addOverride(
283             R.dimen.status_bar_system_icons_height,
284             newHeight
285         )
286 
287         view.onConfigurationChanged(Configuration())
288 
289         assertThat(systemIconsContainer.layoutParams.height).isEqualTo(newHeight)
290     }
291 
292     @Test
onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsetsnull293     fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
294         val insets = Insets.of(/* left = */ 90, /* top = */ 10, /* right = */ 45, /* bottom = */ 50)
295         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
296             .thenReturn(insets)
297 
298         view.onApplyWindowInsets(WindowInsets(Rect()))
299 
300         assertThat(view.paddingLeft).isEqualTo(insets.left)
301         assertThat(view.paddingTop).isEqualTo(insets.top)
302         assertThat(view.paddingRight).isEqualTo(insets.right)
303         assertThat(view.paddingBottom).isEqualTo(0)
304     }
305 
306     private class TestTouchEventHandler : Gefingerpoken {
307         var lastInterceptEvent: MotionEvent? = null
308         var lastEvent: MotionEvent? = null
309         var handleTouchReturnValue: Boolean = false
310 
onInterceptTouchEventnull311         override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
312             lastInterceptEvent = event
313             return handleTouchReturnValue
314         }
315 
onTouchEventnull316         override fun onTouchEvent(event: MotionEvent?): Boolean {
317             lastEvent = event
318             return handleTouchReturnValue
319         }
320     }
321 
createStatusBarViewnull322     private fun createStatusBarView() =
323         LayoutInflater.from(context)
324             .inflate(
325                 R.layout.status_bar,
326                 /* root= */ FrameLayout(context),
327                 /* attachToRoot = */ false
328             ) as PhoneStatusBarView
329 
330     private fun emptyWindowInsets() =
331         WindowInsets(
332             /* typeInsetsMap = */ arrayOf(),
333             /* typeMaxInsetsMap = */ arrayOf(),
334             /* typeVisibilityMap = */ booleanArrayOf(),
335             /* isRound = */ false,
336             /* forceConsumingTypes = */ 0,
337             /* suppressScrimTypes = */ 0,
338             /* displayCutout = */ DisplayCutout.NO_CUTOUT,
339             /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
340             /* privacyIndicatorBounds = */ PrivacyIndicatorBounds(),
341             /* displayShape = */ DisplayShape.NONE,
342             /* compatInsetsTypes = */ 0,
343             /* compatIgnoreVisibility = */ false,
344             /* typeBoundingRectsMap = */ arrayOf(),
345             /* typeMaxBoundingRectsMap = */ arrayOf(),
346             /* frameWidth = */ 0,
347             /* frameHeight = */ 0
348         )
349 }
350