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