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.platform.test.annotations.DisableFlags
23 import android.platform.test.annotations.EnableFlags
24 import android.testing.TestableLooper.RunWithLooper
25 import android.view.DisplayCutout
26 import android.view.DisplayShape
27 import android.view.LayoutInflater
28 import android.view.MotionEvent
29 import android.view.PrivacyIndicatorBounds
30 import android.view.RoundedCorners
31 import android.view.View
32 import android.view.WindowInsets
33 import android.widget.FrameLayout
34 import androidx.test.filters.SmallTest
35 import com.android.systemui.Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT
36 import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
37 import com.android.systemui.Gefingerpoken
38 import com.android.systemui.SysuiTestCase
39 import com.android.systemui.res.R
40 import com.android.systemui.statusbar.window.StatusBarWindowController
41 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
42 import com.android.systemui.util.mockito.mock
43 import com.android.systemui.util.mockito.whenever
44 import com.google.common.truth.Truth.assertThat
45 import org.junit.Before
46 import org.junit.Test
47 import org.mockito.Mockito.never
48 import org.mockito.Mockito.spy
49 import org.mockito.Mockito.times
50 import org.mockito.Mockito.verify
51 
52 @SmallTest
53 @RunWithLooper(setAsMainLooper = true)
54 class PhoneStatusBarViewTest : SysuiTestCase() {
55 
56     private lateinit var view: PhoneStatusBarView
57     private val systemIconsContainer: View
58         get() = view.requireViewById(R.id.system_icons)
59 
60     private val windowController = mock<StatusBarWindowController>()
61     private val windowControllerStore = mock<StatusBarWindowControllerStore>()
62 
63     @Before
setUpnull64     fun setUp() {
65         whenever(windowControllerStore.defaultDisplay).thenReturn(windowController)
66         mDependency.injectTestDependency(
67             StatusBarWindowControllerStore::class.java,
68             windowControllerStore,
69         )
70         context.ensureTestableResources()
71         view = spy(createStatusBarView())
72         whenever(view.rootWindowInsets).thenReturn(emptyWindowInsets())
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
87     @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
onInterceptTouchEvent_flagOff_listenerNotifiednull88     fun onInterceptTouchEvent_flagOff_listenerNotified() {
89         val handler = TestTouchEventHandler()
90         view.setTouchEventHandler(handler)
91 
92         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
93         view.onInterceptTouchEvent(event)
94 
95         assertThat(handler.lastInterceptEvent).isEqualTo(event)
96     }
97 
98     @Test
99     @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
onInterceptTouchEvent_flagOn_listenerNotifiednull100     fun onInterceptTouchEvent_flagOn_listenerNotified() {
101         val handler = TestTouchEventHandler()
102         view.setTouchEventHandler(handler)
103 
104         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
105         view.onInterceptTouchEvent(event)
106 
107         assertThat(handler.lastInterceptEvent).isEqualTo(event)
108     }
109 
110     @Test
111     @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
onInterceptTouchEvent_listenerReturnsFalse_flagOff_viewReturnsFalsenull112     fun onInterceptTouchEvent_listenerReturnsFalse_flagOff_viewReturnsFalse() {
113         val handler = TestTouchEventHandler()
114         view.setTouchEventHandler(handler)
115         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
116 
117         handler.handleTouchReturnValue = false
118 
119         assertThat(view.onInterceptTouchEvent(event)).isFalse()
120     }
121 
122     @Test
123     @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
onInterceptTouchEvent_listenerReturnsFalse_flagOn_viewReturnsFalsenull124     fun onInterceptTouchEvent_listenerReturnsFalse_flagOn_viewReturnsFalse() {
125         val handler = TestTouchEventHandler()
126         view.setTouchEventHandler(handler)
127         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
128 
129         handler.handleTouchReturnValue = false
130 
131         assertThat(view.onInterceptTouchEvent(event)).isFalse()
132     }
133 
134     @Test
135     @DisableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
onInterceptTouchEvent_listenerReturnsTrue_flagOff_viewReturnsFalsenull136     fun onInterceptTouchEvent_listenerReturnsTrue_flagOff_viewReturnsFalse() {
137         val handler = TestTouchEventHandler()
138         view.setTouchEventHandler(handler)
139         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
140 
141         handler.handleTouchReturnValue = true
142 
143         assertThat(view.onInterceptTouchEvent(event)).isFalse()
144     }
145 
146     @Test
147     @EnableFlags(FLAG_STATUS_BAR_SWIPE_OVER_CHIP)
onInterceptTouchEvent_listenerReturnsTrue_flagOn_viewReturnsTruenull148     fun onInterceptTouchEvent_listenerReturnsTrue_flagOn_viewReturnsTrue() {
149         val handler = TestTouchEventHandler()
150         view.setTouchEventHandler(handler)
151         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
152 
153         handler.handleTouchReturnValue = true
154 
155         assertThat(view.onInterceptTouchEvent(event)).isTrue()
156     }
157 
158     @Test
onTouchEvent_listenerReturnsTrue_viewReturnsTruenull159     fun onTouchEvent_listenerReturnsTrue_viewReturnsTrue() {
160         val handler = TestTouchEventHandler()
161         view.setTouchEventHandler(handler)
162         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
163 
164         handler.handleTouchReturnValue = true
165 
166         assertThat(view.onTouchEvent(event)).isTrue()
167     }
168 
169     @Test
onTouchEvent_listenerReturnsFalse_viewReturnsFalsenull170     fun onTouchEvent_listenerReturnsFalse_viewReturnsFalse() {
171         val handler = TestTouchEventHandler()
172         view.setTouchEventHandler(handler)
173         val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
174 
175         handler.handleTouchReturnValue = false
176 
177         assertThat(view.onTouchEvent(event)).isFalse()
178     }
179 
180     @Test
onTouchEvent_noListener_noCrashnull181     fun onTouchEvent_noListener_noCrash() {
182         view.onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
183         // No assert needed, just testing no crash
184     }
185 
186     @Test
187     @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
onAttachedToWindow_flagOff_updatesWindowHeightnull188     fun onAttachedToWindow_flagOff_updatesWindowHeight() {
189         view.onAttachedToWindow()
190 
191         verify(windowController).refreshStatusBarHeight()
192     }
193 
194     @Test
195     @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
onAttachedToWindow_flagOn_doesNotUpdateWindowHeightnull196     fun onAttachedToWindow_flagOn_doesNotUpdateWindowHeight() {
197         view.onAttachedToWindow()
198 
199         verify(windowController, never()).refreshStatusBarHeight()
200     }
201 
202     @Test
203     @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
onConfigurationChanged_flagOff_updatesWindowHeightnull204     fun onConfigurationChanged_flagOff_updatesWindowHeight() {
205         view.onConfigurationChanged(Configuration())
206 
207         verify(windowController).refreshStatusBarHeight()
208     }
209 
210     @Test
211     @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
onConfigurationChanged_flagOn_doesNotUpdateWindowHeightnull212     fun onConfigurationChanged_flagOn_doesNotUpdateWindowHeight() {
213         view.onConfigurationChanged(Configuration())
214 
215         verify(windowController, never()).refreshStatusBarHeight()
216     }
217 
218     @Test
219     @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimesnull220     fun onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimes() {
221         view.onConfigurationChanged(Configuration())
222         view.onConfigurationChanged(Configuration())
223         view.onConfigurationChanged(Configuration())
224         view.onConfigurationChanged(Configuration())
225 
226         verify(windowController, times(4)).refreshStatusBarHeight()
227     }
228 
229     @Test
230     @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeightnull231     fun onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeight() {
232         view.onConfigurationChanged(Configuration())
233         view.onConfigurationChanged(Configuration())
234         view.onConfigurationChanged(Configuration())
235         view.onConfigurationChanged(Configuration())
236 
237         verify(windowController, never()).refreshStatusBarHeight()
238     }
239 
240     @Test
onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsetsnull241     fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
242         val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
243         view.setInsetsFetcher { insets }
244 
245         view.onAttachedToWindow()
246 
247         assertThat(view.paddingLeft).isEqualTo(insets.left)
248         assertThat(view.paddingTop).isEqualTo(insets.top)
249         assertThat(view.paddingRight).isEqualTo(insets.right)
250         assertThat(view.paddingBottom).isEqualTo(0)
251     }
252 
253     @Test
onAttachedToWindow_noInsetsFetcher_noCrashnull254     fun onAttachedToWindow_noInsetsFetcher_noCrash() {
255         // Don't call `PhoneStatusBarView.setInsetsFetcher`
256 
257         // WHEN the view is attached
258         view.onAttachedToWindow()
259 
260         // THEN there's no crash, and the padding stays as it was
261         assertThat(view.paddingLeft).isEqualTo(0)
262         assertThat(view.paddingTop).isEqualTo(0)
263         assertThat(view.paddingRight).isEqualTo(0)
264         assertThat(view.paddingBottom).isEqualTo(0)
265     }
266 
267     @Test
onAttachedToWindow_thenGetsInsetsFetcher_insetsUpdatednull268     fun onAttachedToWindow_thenGetsInsetsFetcher_insetsUpdated() {
269         view.onAttachedToWindow()
270 
271         // WHEN the insets fetcher is set after the view is attached
272         val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
273         view.setInsetsFetcher { insets }
274 
275         // THEN the insets are updated
276         assertThat(view.paddingLeft).isEqualTo(insets.left)
277         assertThat(view.paddingTop).isEqualTo(insets.top)
278         assertThat(view.paddingRight).isEqualTo(insets.right)
279         assertThat(view.paddingBottom).isEqualTo(0)
280     }
281 
282 
283     @Test
onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsetsnull284     fun onConfigurationChanged_updatesLeftTopRightPaddingsBasedOnInsets() {
285         val insets = Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
286         view.setInsetsFetcher { insets }
287 
288         view.onConfigurationChanged(Configuration())
289 
290         assertThat(view.paddingLeft).isEqualTo(insets.left)
291         assertThat(view.paddingTop).isEqualTo(insets.top)
292         assertThat(view.paddingRight).isEqualTo(insets.right)
293         assertThat(view.paddingBottom).isEqualTo(0)
294     }
295 
296     @Test
onConfigurationChanged_noInsetsFetcher_noCrashnull297     fun onConfigurationChanged_noInsetsFetcher_noCrash() {
298         // Don't call `PhoneStatusBarView.setInsetsFetcher`
299 
300         // WHEN the view is attached
301         view.onConfigurationChanged(Configuration())
302 
303         // THEN there's no crash, and the padding stays as it was
304         assertThat(view.paddingLeft).isEqualTo(0)
305         assertThat(view.paddingTop).isEqualTo(0)
306         assertThat(view.paddingRight).isEqualTo(0)
307         assertThat(view.paddingBottom).isEqualTo(0)
308     }
309 
310     @Test
onConfigurationChanged_noRelevantChange_doesNotUpdateInsetsnull311     fun onConfigurationChanged_noRelevantChange_doesNotUpdateInsets() {
312         val previousInsets =
313             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
314         val newInsets = Insets.NONE
315 
316         var useNewInsets = false
317         val insetsFetcher = PhoneStatusBarView.InsetsFetcher {
318             if (useNewInsets) {
319                 newInsets
320             } else {
321                 previousInsets
322             }
323         }
324         view.setInsetsFetcher(insetsFetcher)
325 
326         context.orCreateTestableResources.overrideConfiguration(Configuration())
327         view.onAttachedToWindow()
328 
329         useNewInsets = true
330         view.onConfigurationChanged(Configuration())
331 
332         assertThat(view.paddingLeft).isEqualTo(previousInsets.left)
333         assertThat(view.paddingTop).isEqualTo(previousInsets.top)
334         assertThat(view.paddingRight).isEqualTo(previousInsets.right)
335         assertThat(view.paddingBottom).isEqualTo(0)
336     }
337 
338     @Test
onConfigurationChanged_densityChanged_updatesInsetsnull339     fun onConfigurationChanged_densityChanged_updatesInsets() {
340         val previousInsets =
341             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
342         val newInsets = Insets.NONE
343 
344         var useNewInsets = false
345         val insetsFetcher = PhoneStatusBarView.InsetsFetcher {
346             if (useNewInsets) {
347                 newInsets
348             } else {
349                 previousInsets
350             }
351         }
352         view.setInsetsFetcher(insetsFetcher)
353 
354         val configuration = Configuration()
355         configuration.densityDpi = 123
356         context.orCreateTestableResources.overrideConfiguration(configuration)
357         view.onAttachedToWindow()
358 
359         useNewInsets = true
360         configuration.densityDpi = 456
361         view.onConfigurationChanged(configuration)
362 
363         assertThat(view.paddingLeft).isEqualTo(newInsets.left)
364         assertThat(view.paddingTop).isEqualTo(newInsets.top)
365         assertThat(view.paddingRight).isEqualTo(newInsets.right)
366         assertThat(view.paddingBottom).isEqualTo(0)
367     }
368 
369     @Test
onConfigurationChanged_fontScaleChanged_updatesInsetsnull370     fun onConfigurationChanged_fontScaleChanged_updatesInsets() {
371         val previousInsets =
372             Insets.of(/* left= */ 40, /* top= */ 30, /* right= */ 20, /* bottom= */ 10)
373         val newInsets = Insets.NONE
374 
375         var useNewInsets = false
376         val insetsFetcher = PhoneStatusBarView.InsetsFetcher {
377             if (useNewInsets) {
378                 newInsets
379             } else {
380                 previousInsets
381             }
382         }
383         view.setInsetsFetcher(insetsFetcher)
384 
385         val configuration = Configuration()
386         configuration.fontScale = 1f
387         context.orCreateTestableResources.overrideConfiguration(configuration)
388         view.onAttachedToWindow()
389 
390         useNewInsets = true
391         configuration.fontScale = 2f
392         view.onConfigurationChanged(configuration)
393 
394         assertThat(view.paddingLeft).isEqualTo(newInsets.left)
395         assertThat(view.paddingTop).isEqualTo(newInsets.top)
396         assertThat(view.paddingRight).isEqualTo(newInsets.right)
397         assertThat(view.paddingBottom).isEqualTo(0)
398     }
399 
400     @Test
onConfigurationChanged_systemIconsHeightChanged_containerHeightIsUpdatednull401     fun onConfigurationChanged_systemIconsHeightChanged_containerHeightIsUpdated() {
402         val newHeight = 123456
403         context.orCreateTestableResources.addOverride(
404             R.dimen.status_bar_system_icons_height,
405             newHeight
406         )
407 
408         view.onConfigurationChanged(Configuration())
409 
410         assertThat(systemIconsContainer.layoutParams.height).isEqualTo(newHeight)
411     }
412 
413     @Test
onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsetsnull414     fun onApplyWindowInsets_updatesLeftTopRightPaddingsBasedOnInsets() {
415         val insets = Insets.of(/* left= */ 90, /* top= */ 10, /* right= */ 45, /* bottom= */ 50)
416         view.setInsetsFetcher { insets }
417 
418         view.onApplyWindowInsets(WindowInsets(Rect()))
419 
420         assertThat(view.paddingLeft).isEqualTo(insets.left)
421         assertThat(view.paddingTop).isEqualTo(insets.top)
422         assertThat(view.paddingRight).isEqualTo(insets.right)
423         assertThat(view.paddingBottom).isEqualTo(0)
424     }
425 
426     private class TestTouchEventHandler : Gefingerpoken {
427         var lastInterceptEvent: MotionEvent? = null
428         var lastEvent: MotionEvent? = null
429         var handleTouchReturnValue: Boolean = false
430 
onInterceptTouchEventnull431         override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
432             lastInterceptEvent = event
433             return handleTouchReturnValue
434         }
435 
onTouchEventnull436         override fun onTouchEvent(event: MotionEvent?): Boolean {
437             lastEvent = event
438             return handleTouchReturnValue
439         }
440     }
441 
createStatusBarViewnull442     private fun createStatusBarView() =
443         LayoutInflater.from(context)
444             .inflate(
445                 R.layout.status_bar,
446                 /* root= */ FrameLayout(context),
447                 /* attachToRoot = */ false
448             ) as PhoneStatusBarView
449 
450     private fun emptyWindowInsets() =
451         WindowInsets(
452             /* typeInsetsMap = */ arrayOf(),
453             /* typeMaxInsetsMap = */ arrayOf(),
454             /* typeVisibilityMap = */ booleanArrayOf(),
455             /* isRound = */ false,
456             /* forceConsumingTypes = */ 0,
457             /* forceConsumingOpaqueCaptionBar = */ false,
458             /* suppressScrimTypes = */ 0,
459             /* displayCutout = */ DisplayCutout.NO_CUTOUT,
460             /* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
461             /* privacyIndicatorBounds = */ PrivacyIndicatorBounds(),
462             /* displayShape = */ DisplayShape.NONE,
463             /* compatInsetsTypes = */ 0,
464             /* compatIgnoreVisibility = */ false,
465             /* typeBoundingRectsMap = */ arrayOf(),
466             /* typeMaxBoundingRectsMap = */ arrayOf(),
467             /* frameWidth = */ 0,
468             /* frameHeight = */ 0
469         )
470 }
471