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