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.shade; 18 19 import static android.view.MotionEvent.ACTION_DOWN; 20 import static android.view.MotionEvent.ACTION_MOVE; 21 import static android.view.MotionEvent.ACTION_POINTER_DOWN; 22 import static android.view.MotionEvent.ACTION_UP; 23 import static android.view.MotionEvent.BUTTON_SECONDARY; 24 import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY; 25 26 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; 27 import static com.android.systemui.statusbar.StatusBarState.SHADE; 28 29 import static com.google.common.truth.Truth.assertThat; 30 31 import static org.mockito.ArgumentMatchers.anyFloat; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.atLeastOnce; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.testing.AndroidTestingRunner; 39 import android.testing.TestableLooper; 40 import android.view.MotionEvent; 41 42 import androidx.test.filters.SmallTest; 43 44 import com.android.systemui.R; 45 import com.android.systemui.plugins.qs.QS; 46 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.mockito.ArgumentCaptor; 50 51 import java.util.List; 52 53 @SmallTest 54 @RunWith(AndroidTestingRunner.class) 55 @TestableLooper.RunWithLooper(setAsMainLooper = true) 56 public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest { 57 58 @Test testCloseQsSideEffects()59 public void testCloseQsSideEffects() { 60 enableSplitShade(true); 61 mQsController.setExpandImmediate(true); 62 mQsController.setExpanded(true); 63 mQsController.closeQs(); 64 65 assertThat(mQsController.getExpanded()).isEqualTo(false); 66 assertThat(mQsController.isExpandImmediate()).isEqualTo(false); 67 } 68 69 @Test testLargeScreenHeaderMadeActiveForLargeScreen()70 public void testLargeScreenHeaderMadeActiveForLargeScreen() { 71 mStatusBarStateController.setState(SHADE); 72 when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true); 73 mQsController.updateResources(); 74 verify(mShadeHeaderController).setLargeScreenActive(true); 75 76 when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false); 77 mQsController.updateResources(); 78 verify(mShadeHeaderController).setLargeScreenActive(false); 79 } 80 81 @Test testPanelStaysOpenWhenClosingQs()82 public void testPanelStaysOpenWhenClosingQs() { 83 mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); 84 85 float shadeExpandedHeight = mQsController.getShadeExpandedHeight(); 86 mQsController.animateCloseQs(false); 87 88 assertThat(mQsController.getShadeExpandedHeight()).isEqualTo(shadeExpandedHeight); 89 } 90 91 @Test interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking()92 public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { 93 mQsController.setQs(mQs); 94 95 mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); 96 mQsController.onIntercept( 97 createMotionEvent(0, 0, ACTION_DOWN)); 98 mQsController.onIntercept( 99 createMotionEvent(0, 500, ACTION_MOVE)); 100 101 assertThat(mQsController.isTracking()).isTrue(); 102 } 103 104 @Test interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking()105 public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() { 106 enableSplitShade(true); 107 mQsController.setQs(mQs); 108 109 mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); 110 mQsController.onIntercept( 111 createMotionEvent(0, 0, ACTION_DOWN)); 112 mQsController.onIntercept( 113 createMotionEvent(0, 500, ACTION_MOVE)); 114 115 assertThat(mQsController.isTracking()).isFalse(); 116 } 117 118 @Test interceptTouch_downBetweenFullyCollapsedAndExpanded()119 public void interceptTouch_downBetweenFullyCollapsedAndExpanded() { 120 mQsController.setQs(mQs); 121 when(mQs.getDesiredHeight()).thenReturn(QS_FRAME_BOTTOM); 122 mQsController.onHeightChanged(); 123 mQsController.setExpansionHeight(QS_FRAME_BOTTOM / 2f); 124 125 assertThat(mQsController.onIntercept( 126 createMotionEvent(0, QS_FRAME_BOTTOM / 2, ACTION_DOWN))).isTrue(); 127 } 128 129 @Test onTouch_moveActionSetsCorrectExpansionHeight()130 public void onTouch_moveActionSetsCorrectExpansionHeight() { 131 mQsController.setQs(mQs); 132 when(mQs.getDesiredHeight()).thenReturn(QS_FRAME_BOTTOM); 133 mQsController.onHeightChanged(); 134 mQsController.setExpansionHeight(QS_FRAME_BOTTOM / 2f); 135 mQsController.handleTouch( 136 createMotionEvent(0, QS_FRAME_BOTTOM / 4, ACTION_DOWN), false, false); 137 assertThat(mQsController.isTracking()).isTrue(); 138 mQsController.handleTouch( 139 createMotionEvent(0, QS_FRAME_BOTTOM / 4 + 1, ACTION_MOVE), false, false); 140 141 assertThat(mQsController.getExpansionHeight()).isEqualTo(QS_FRAME_BOTTOM / 2 + 1); 142 } 143 144 @Test handleTouch_downActionInQsArea()145 public void handleTouch_downActionInQsArea() { 146 mQsController.setQs(mQs); 147 mQsController.setBarState(SHADE); 148 mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 0.5f); 149 150 MotionEvent event = 151 createMotionEvent(QS_FRAME_WIDTH / 2, QS_FRAME_BOTTOM / 2, ACTION_DOWN); 152 mQsController.handleTouch(event, false, false); 153 154 assertThat(mQsController.isTracking()).isTrue(); 155 assertThat(mQsController.getInitialTouchY()).isEqualTo(QS_FRAME_BOTTOM / 2); 156 } 157 158 @Test handleTouch_qsTouchedWhileCollapsingDisablesTracking()159 public void handleTouch_qsTouchedWhileCollapsingDisablesTracking() { 160 mQsController.handleTouch( 161 createMotionEvent(0, QS_FRAME_BOTTOM, ACTION_DOWN), false, false); 162 mQsController.setLastShadeFlingWasExpanding(false); 163 mQsController.handleTouch( 164 createMotionEvent(0, QS_FRAME_BOTTOM / 2, ACTION_MOVE), false, true); 165 MotionEvent secondTouch = createMotionEvent(0, QS_FRAME_TOP, ACTION_DOWN); 166 mQsController.handleTouch(secondTouch, false, true); 167 assertThat(mQsController.isTracking()).isFalse(); 168 } 169 170 @Test handleTouch_qsTouchedWhileExpanding()171 public void handleTouch_qsTouchedWhileExpanding() { 172 mQsController.setQs(mQs); 173 mQsController.handleTouch( 174 createMotionEvent(100, 100, ACTION_DOWN), false, false); 175 mQsController.handleTouch( 176 createMotionEvent(0, QS_FRAME_BOTTOM / 2, ACTION_MOVE), false, false); 177 mQsController.setLastShadeFlingWasExpanding(true); 178 mQsController.handleTouch( 179 createMotionEvent(0, QS_FRAME_TOP, ACTION_DOWN), false, false); 180 assertThat(mQsController.isTracking()).isTrue(); 181 } 182 183 @Test handleTouch_isConflictingExpansionGestureSet()184 public void handleTouch_isConflictingExpansionGestureSet() { 185 assertThat(mQsController.isConflictingExpansionGesture()).isFalse(); 186 mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); 187 mQsController.handleTouch(MotionEvent.obtain(0L /* downTime */, 188 0L /* eventTime */, ACTION_DOWN, 0f /* x */, 0f /* y */, 189 0 /* metaState */), false, false); 190 assertThat(mQsController.isConflictingExpansionGesture()).isTrue(); 191 } 192 193 @Test handleTouch_isConflictingExpansionGestureSet_cancel()194 public void handleTouch_isConflictingExpansionGestureSet_cancel() { 195 mQsController.setShadeExpansion(/* shadeExpandedHeight= */ 1, /* expandedFraction=*/ 1); 196 mQsController.handleTouch(createMotionEvent(0, 0, ACTION_DOWN), false, false); 197 assertThat(mQsController.isConflictingExpansionGesture()).isTrue(); 198 mQsController.handleTouch(createMotionEvent(0, 0, ACTION_UP), true, true); 199 assertThat(mQsController.isConflictingExpansionGesture()).isFalse(); 200 } 201 202 @Test handleTouch_twoFingerExpandPossibleConditions()203 public void handleTouch_twoFingerExpandPossibleConditions() { 204 assertThat(mQsController.isTwoFingerExpandPossible()).isFalse(); 205 mQsController.handleTouch(createMotionEvent(0, 0, ACTION_DOWN), true, false); 206 assertThat(mQsController.isTwoFingerExpandPossible()).isTrue(); 207 } 208 209 @Test handleTouch_twoFingerDrag()210 public void handleTouch_twoFingerDrag() { 211 mQsController.setQs(mQs); 212 mQsController.setStatusBarMinHeight(1); 213 mQsController.setTwoFingerExpandPossible(true); 214 mQsController.handleTouch( 215 createMultitouchMotionEvent(ACTION_POINTER_DOWN), false, false); 216 assertThat(mQsController.isExpandImmediate()).isTrue(); 217 verify(mQs).setListening(true); 218 } 219 220 @Test onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS()221 public void onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS() { 222 setIsFullWidth(true); 223 mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); 224 225 verify(mQSFragment).setIsNotificationPanelFullWidth(true); 226 } 227 228 @Test onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS()229 public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() { 230 setIsFullWidth(false); 231 mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); 232 233 verify(mQSFragment).setIsNotificationPanelFullWidth(false); 234 } 235 236 @Test setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness()237 public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() { 238 float squishinessFraction = 0.456f; 239 mQsController.setQs(mQs); 240 when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) 241 .thenReturn(squishinessFraction); 242 when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) 243 .thenReturn(0.987f); 244 // Call setTransitionToFullShadeAmount to get into the full shade transition in progress 245 // state. 246 mLockscreenShadeTransitionCallback.setTransitionToFullShadeAmount(234, false, 0); 247 248 mQsController.setExpansionHeight(123); 249 250 // First for setTransitionToFullShadeAmount and then setQsExpansion 251 verify(mQs, times(2)).setQsExpansion(anyFloat(), anyFloat(), anyFloat(), 252 eq(squishinessFraction) 253 ); 254 } 255 256 @Test setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness()257 public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() { 258 float lsSquishinessFraction = 0.456f; 259 float nsslSquishinessFraction = 0.987f; 260 mQsController.setQs(mQs); 261 when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) 262 .thenReturn(lsSquishinessFraction); 263 when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) 264 .thenReturn(nsslSquishinessFraction); 265 266 mQsController.setExpansionHeight(123); 267 268 verify(mQs).setQsExpansion(anyFloat(), anyFloat(), anyFloat(), eq(nsslSquishinessFraction) 269 ); 270 } 271 272 @Test updateExpansion_expandImmediateOrAlreadyExpanded_usesFullSquishiness()273 public void updateExpansion_expandImmediateOrAlreadyExpanded_usesFullSquishiness() { 274 mQsController.setQs(mQs); 275 when(mQs.getDesiredHeight()).thenReturn(100); 276 mQsController.onHeightChanged(); 277 278 mQsController.setExpandImmediate(true); 279 mQsController.setExpanded(false); 280 mQsController.updateExpansion(); 281 mQsController.setExpandImmediate(false); 282 mQsController.setExpanded(true); 283 mQsController.updateExpansion(); 284 verify(mQs, times(2)).setQsExpansion(0, 0, 0, 1); 285 } 286 287 @Test shadeExpanded_onKeyguard()288 public void shadeExpanded_onKeyguard() { 289 mStatusBarStateController.setState(KEYGUARD); 290 // set maxQsExpansion in NPVC 291 int maxQsExpansion = 123; 292 mQsController.setQs(mQs); 293 when(mQs.getDesiredHeight()).thenReturn(maxQsExpansion); 294 295 int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange(); 296 mQsController.handleShadeLayoutChanged(oldMaxHeight); 297 298 mQsController.setExpansionHeight(maxQsExpansion); 299 assertThat(mQsController.computeExpansionFraction()).isEqualTo(1f); 300 } 301 302 @Test handleTouch_splitShadeAndtouchXOutsideQs()303 public void handleTouch_splitShadeAndtouchXOutsideQs() { 304 enableSplitShade(true); 305 306 assertThat(mQsController.handleTouch(createMotionEvent( 307 QS_FRAME_WIDTH + 1, QS_FRAME_BOTTOM - 1, ACTION_DOWN), 308 false, false)).isFalse(); 309 } 310 311 @Test isOpenQsEvent_twoFingerDrag()312 public void isOpenQsEvent_twoFingerDrag() { 313 assertThat(mQsController.isOpenQsEvent( 314 createMultitouchMotionEvent(ACTION_POINTER_DOWN))).isTrue(); 315 } 316 317 @Test isOpenQsEvent_stylusButtonClickDrag()318 public void isOpenQsEvent_stylusButtonClickDrag() { 319 MotionEvent event = createMotionEvent(0, 0, ACTION_DOWN); 320 event.setButtonState(BUTTON_STYLUS_PRIMARY); 321 322 assertThat(mQsController.isOpenQsEvent(event)).isTrue(); 323 } 324 325 @Test isOpenQsEvent_mouseButtonClickDrag()326 public void isOpenQsEvent_mouseButtonClickDrag() { 327 MotionEvent event = createMotionEvent(0, 0, ACTION_DOWN); 328 event.setButtonState(BUTTON_SECONDARY); 329 330 assertThat(mQsController.isOpenQsEvent(event)).isTrue(); 331 } 332 333 @Test shadeClosed_onLockscreen_inSplitShade_setsQsNotVisible()334 public void shadeClosed_onLockscreen_inSplitShade_setsQsNotVisible() { 335 mQsController.setQs(mQs); 336 enableSplitShade(true); 337 lockScreen(); 338 339 closeLockedQS(); 340 341 assertQsVisible(false); 342 } 343 344 @Test shadeOpened_onLockscreen_inSplitShade_setsQsVisible()345 public void shadeOpened_onLockscreen_inSplitShade_setsQsVisible() { 346 mQsController.setQs(mQs); 347 enableSplitShade(true); 348 lockScreen(); 349 350 openLockedQS(); 351 352 assertQsVisible(true); 353 } 354 355 @Test shadeClosed_onLockscreen_inSingleShade_setsQsNotVisible()356 public void shadeClosed_onLockscreen_inSingleShade_setsQsNotVisible() { 357 mQsController.setQs(mQs); 358 enableSplitShade(false); 359 lockScreen(); 360 361 closeLockedQS(); 362 363 verify(mQs).setQsVisible(false); 364 } 365 366 @Test shadeOpened_onLockscreen_inSingleShade_setsQsVisible()367 public void shadeOpened_onLockscreen_inSingleShade_setsQsVisible() { 368 mQsController.setQs(mQs); 369 enableSplitShade(false); 370 lockScreen(); 371 372 openLockedQS(); 373 374 verify(mQs).setQsVisible(true); 375 } 376 377 @Test calculateBottomCornerRadius_scrimScaleMax()378 public void calculateBottomCornerRadius_scrimScaleMax() { 379 when(mScrimController.getBackScaling()).thenReturn(1.0f); 380 assertThat(mQsController.calculateBottomCornerRadius(0.0f)).isEqualTo(0); 381 } 382 383 @Test calculateBottomCornerRadius_scrimScaleMin()384 public void calculateBottomCornerRadius_scrimScaleMin() { 385 when(mScrimController.getBackScaling()) 386 .thenReturn(mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE); 387 assertThat(mQsController.calculateBottomCornerRadius(0.0f)) 388 .isEqualTo(mQsController.getScrimCornerRadius()); 389 } 390 391 @Test calculateBottomCornerRadius_scrimScaleCutoff()392 public void calculateBottomCornerRadius_scrimScaleCutoff() { 393 float ratio = 1 / mQsController.calculateBottomRadiusProgress(); 394 float cutoffScale = 1 - mNotificationPanelViewController.SHADE_BACK_ANIM_MIN_SCALE / ratio; 395 when(mScrimController.getBackScaling()) 396 .thenReturn(cutoffScale); 397 assertThat(mQsController.calculateBottomCornerRadius(0.0f)) 398 .isEqualTo(mQsController.getScrimCornerRadius()); 399 } 400 lockScreen()401 private void lockScreen() { 402 mQsController.setBarState(KEYGUARD); 403 } 404 openLockedQS()405 private void openLockedQS() { 406 when(mLockscreenShadeTransitionController.getQSDragProgress()) 407 .thenReturn((float) DEFAULT_HEIGHT); 408 mLockscreenShadeTransitionCallback.setTransitionToFullShadeAmount( 409 /* pxAmount= */ DEFAULT_HEIGHT, 410 /* animate=*/ false, 411 /* delay= */ 0 412 ); 413 } 414 closeLockedQS()415 private void closeLockedQS() { 416 when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(0f); 417 mLockscreenShadeTransitionCallback.setTransitionToFullShadeAmount( 418 /* pxAmount= */ 0, 419 /* animate=*/ false, 420 /* delay= */ 0 421 ); 422 } 423 setSplitShadeHeightProperties()424 private void setSplitShadeHeightProperties() { 425 // In split shade, min = max 426 when(mQs.getQsMinExpansionHeight()).thenReturn(DEFAULT_MIN_HEIGHT_SPLIT_SHADE); 427 when(mQs.getDesiredHeight()).thenReturn(DEFAULT_HEIGHT); 428 mQsController.updateMinHeight(); 429 mQsController.onHeightChanged(); 430 } 431 setDefaultHeightProperties()432 private void setDefaultHeightProperties() { 433 when(mQs.getQsMinExpansionHeight()).thenReturn(DEFAULT_MIN_HEIGHT); 434 when(mQs.getDesiredHeight()).thenReturn(DEFAULT_HEIGHT); 435 mQsController.updateMinHeight(); 436 mQsController.onHeightChanged(); 437 } 438 createMotionEvent(int x, int y, int action)439 private static MotionEvent createMotionEvent(int x, int y, int action) { 440 return MotionEvent.obtain(0, 0, action, x, y, 0); 441 } 442 443 // Creates an empty multitouch event for now createMultitouchMotionEvent(int action)444 private static MotionEvent createMultitouchMotionEvent(int action) { 445 return MotionEvent.obtain(0, 0, action, 2, 446 new MotionEvent.PointerProperties[] { 447 new MotionEvent.PointerProperties(), 448 new MotionEvent.PointerProperties() 449 }, 450 new MotionEvent.PointerCoords[] { 451 new MotionEvent.PointerCoords(), 452 new MotionEvent.PointerCoords() 453 }, 0, 0, 0, 0, 0, 0, 0, 0); 454 } 455 enableSplitShade(boolean enabled)456 private void enableSplitShade(boolean enabled) { 457 when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled); 458 mQsController.updateResources(); 459 if (enabled) { 460 setSplitShadeHeightProperties(); 461 } else { 462 setDefaultHeightProperties(); 463 } 464 } 465 setIsFullWidth(boolean fullWidth)466 private void setIsFullWidth(boolean fullWidth) { 467 mQsController.setNotificationPanelFullWidth(fullWidth); 468 triggerLayoutChange(); 469 } 470 triggerLayoutChange()471 private void triggerLayoutChange() { 472 int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange(); 473 mQsController.handleShadeLayoutChanged(oldMaxHeight); 474 } 475 assertQsVisible(boolean visible)476 private void assertQsVisible(boolean visible) { 477 ArgumentCaptor<Boolean> visibilityCaptor = ArgumentCaptor.forClass(Boolean.class); 478 verify(mQs, atLeastOnce()).setQsVisible(visibilityCaptor.capture()); 479 List<Boolean> allVisibilities = visibilityCaptor.getAllValues(); 480 boolean lastVisibility = allVisibilities.get(allVisibilities.size() - 1); 481 assertThat(lastVisibility).isEqualTo(visible); 482 } 483 } 484