1 /* 2 * Copyright (C) 2018 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.AdditionalAnswers.returnsFirstArg; 24 import static org.mockito.ArgumentMatchers.anyBoolean; 25 import static org.mockito.ArgumentMatchers.anyInt; 26 import static org.mockito.Mockito.when; 27 28 import android.content.res.Resources; 29 import android.platform.test.annotations.DisableFlags; 30 import android.platform.test.annotations.EnableFlags; 31 32 import androidx.test.ext.junit.runners.AndroidJUnit4; 33 import androidx.test.filters.SmallTest; 34 35 import com.android.systemui.Flags; 36 import com.android.systemui.SysuiTestCase; 37 import com.android.systemui.doze.util.BurnInHelperKt; 38 import com.android.systemui.log.LogBuffer; 39 import com.android.systemui.log.core.FakeLogBuffer; 40 import com.android.systemui.res.R; 41 import com.android.systemui.shade.LargeScreenHeaderHelper; 42 43 import org.junit.After; 44 import org.junit.Before; 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 import org.mockito.Mock; 48 import org.mockito.MockitoAnnotations; 49 import org.mockito.MockitoSession; 50 import org.mockito.quality.Strictness; 51 52 @SmallTest 53 @RunWith(AndroidJUnit4.class) 54 public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { 55 private static final int SCREEN_HEIGHT = 2000; 56 private static final int EMPTY_HEIGHT = 0; 57 private static final float ZERO_DRAG = 0.f; 58 private static final float OPAQUE = 1.f; 59 private static final float TRANSPARENT = 0.f; 60 61 @Mock private Resources mResources; 62 63 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm; 64 private KeyguardClockPositionAlgorithm.Result mClockPosition; 65 66 private MockitoSession mStaticMockSession; 67 68 private float mPanelExpansion; 69 private int mKeyguardStatusBarHeaderHeight; 70 private int mKeyguardStatusHeight; 71 private int mUserSwitchHeight; 72 private float mDark; 73 private float mQsExpansion; 74 private int mCutoutTopInset = 0; 75 private boolean mIsSplitShade = false; 76 private boolean mBypassEnabled = false; 77 private int mUnlockedStackScrollerPadding = 0; 78 private float mUdfpsTop = -1; 79 private float mClockBottom = SCREEN_HEIGHT / 2; 80 private boolean mClockTopAligned; 81 82 @Before setUp()83 public void setUp() { 84 MockitoAnnotations.initMocks(this); 85 mStaticMockSession = mockitoSession() 86 .strictness(Strictness.WARN) 87 .mockStatic(BurnInHelperKt.class) 88 .mockStatic(LargeScreenHeaderHelper.class) 89 .startMocking(); 90 91 LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create(); 92 mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer); 93 when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0); 94 mClockPositionAlgorithm.loadDimens(mContext, mResources); 95 96 mClockPosition = new KeyguardClockPositionAlgorithm.Result(); 97 } 98 99 @After tearDown()100 public void tearDown() { 101 mStaticMockSession.finishMocking(); 102 } 103 104 @Test clockPositionTopOfScreenOnAOD()105 public void clockPositionTopOfScreenOnAOD() { 106 // GIVEN on AOD and clock has 0 height 107 givenAOD(); 108 mKeyguardStatusHeight = EMPTY_HEIGHT; 109 // WHEN the clock position algorithm is run 110 positionClock(); 111 // THEN the clock Y position is the top of the screen 112 assertThat(mClockPosition.clockY).isEqualTo(0); 113 // AND the clock is opaque and positioned on the left. 114 assertThat(mClockPosition.clockX).isEqualTo(0); 115 assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE); 116 } 117 118 @Test clockPositionBelowCutout()119 public void clockPositionBelowCutout() { 120 // GIVEN on AOD and clock has 0 height 121 givenAOD(); 122 mKeyguardStatusHeight = EMPTY_HEIGHT; 123 mCutoutTopInset = 300; 124 // WHEN the clock position algorithm is run 125 positionClock(); 126 // THEN the clock Y position is below the cutout 127 assertThat(mClockPosition.clockY).isEqualTo(300); 128 // AND the clock is opaque and positioned on the left. 129 assertThat(mClockPosition.clockX).isEqualTo(0); 130 assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE); 131 } 132 133 @Test clockPositionAdjustsForKeyguardStatusOnAOD()134 public void clockPositionAdjustsForKeyguardStatusOnAOD() { 135 // GIVEN on AOD with a clock of height 100 136 givenAOD(); 137 mKeyguardStatusHeight = 100; 138 // WHEN the clock position algorithm is run 139 positionClock(); 140 // THEN the clock Y position is at the top 141 assertThat(mClockPosition.clockY).isEqualTo(0); 142 // AND the clock is opaque and positioned on the left. 143 assertThat(mClockPosition.clockX).isEqualTo(0); 144 assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE); 145 } 146 147 @Test clockPositionLargeClockOnAOD()148 public void clockPositionLargeClockOnAOD() { 149 // GIVEN on AOD with a full screen clock 150 givenAOD(); 151 mKeyguardStatusHeight = SCREEN_HEIGHT; 152 // WHEN the clock position algorithm is run 153 positionClock(); 154 // THEN the clock Y position doesn't overflow the screen. 155 assertThat(mClockPosition.clockY).isEqualTo(0); 156 // AND the clock is opaque and positioned on the left. 157 assertThat(mClockPosition.clockX).isEqualTo(0); 158 assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE); 159 } 160 161 @Test clockPositionTopOfScreenOnLockScreen()162 public void clockPositionTopOfScreenOnLockScreen() { 163 // GIVEN on lock screen with clock of 0 height 164 givenLockScreen(); 165 mKeyguardStatusHeight = EMPTY_HEIGHT; 166 // WHEN the clock position algorithm is run 167 positionClock(); 168 // THEN the clock Y position is the top of the screen 169 assertThat(mClockPosition.clockY).isEqualTo(0); 170 // AND the clock is positioned on the left. 171 assertThat(mClockPosition.clockX).isEqualTo(0); 172 } 173 174 @Test clockPositionWithPartialDragOnLockScreen()175 public void clockPositionWithPartialDragOnLockScreen() { 176 // GIVEN dragging up on lock screen 177 givenLockScreen(); 178 mKeyguardStatusHeight = EMPTY_HEIGHT; 179 mPanelExpansion = 0.5f; 180 // WHEN the clock position algorithm is run 181 positionClock(); 182 // THEN the clock Y position adjusts with drag gesture. 183 assertThat(mClockPosition.clockY).isLessThan(1000); 184 // AND the clock is positioned on the left and not fully opaque. 185 assertThat(mClockPosition.clockX).isEqualTo(0); 186 assertThat(mClockPosition.clockAlpha).isLessThan(OPAQUE); 187 } 188 189 @Test clockPositionWithFullDragOnLockScreen()190 public void clockPositionWithFullDragOnLockScreen() { 191 // GIVEN the lock screen is dragged up 192 givenLockScreen(); 193 mKeyguardStatusHeight = EMPTY_HEIGHT; 194 mPanelExpansion = 0.f; 195 // WHEN the clock position algorithm is run 196 positionClock(); 197 // THEN the clock is transparent. 198 assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT); 199 } 200 201 @Test largeClockOnLockScreenIsTransparent()202 public void largeClockOnLockScreenIsTransparent() { 203 // GIVEN on lock screen with a full screen clock 204 givenLockScreen(); 205 mKeyguardStatusHeight = SCREEN_HEIGHT; 206 // WHEN the clock position algorithm is run 207 positionClock(); 208 // THEN the clock is transparent 209 assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT); 210 } 211 212 @Test notifPositionTopOfScreenOnAOD()213 public void notifPositionTopOfScreenOnAOD() { 214 // GIVEN on AOD and clock has 0 height 215 givenAOD(); 216 mKeyguardStatusHeight = EMPTY_HEIGHT; 217 // WHEN the position algorithm is run 218 positionClock(); 219 // THEN the notif padding is 0 (top of screen) 220 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0); 221 } 222 223 @Test notifPositionIndependentOfKeyguardStatusHeightOnAOD()224 public void notifPositionIndependentOfKeyguardStatusHeightOnAOD() { 225 // GIVEN on AOD and clock has a nonzero height 226 givenAOD(); 227 mKeyguardStatusHeight = 100; 228 // WHEN the position algorithm is run 229 positionClock(); 230 // THEN the notif padding adjusts for keyguard status height 231 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(100); 232 } 233 234 @Test notifPositionWithLargeClockOnAOD()235 public void notifPositionWithLargeClockOnAOD() { 236 // GIVEN on AOD and clock has a nonzero height 237 givenAOD(); 238 mKeyguardStatusHeight = SCREEN_HEIGHT; 239 // WHEN the position algorithm is run 240 positionClock(); 241 // THEN the notif padding is, unfortunately, the entire screen. 242 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(SCREEN_HEIGHT); 243 } 244 245 @Test notifPositionMiddleOfScreenOnLockScreen()246 public void notifPositionMiddleOfScreenOnLockScreen() { 247 // GIVEN on lock screen and clock has 0 height 248 givenLockScreen(); 249 mKeyguardStatusHeight = EMPTY_HEIGHT; 250 // WHEN the position algorithm is run 251 positionClock(); 252 // THEN the notif are placed to the top of the screen 253 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0); 254 } 255 256 @Test notifPositionAdjustsForClockHeightOnLockScreen()257 public void notifPositionAdjustsForClockHeightOnLockScreen() { 258 // GIVEN on lock screen and stack scroller has a nonzero height 259 givenLockScreen(); 260 mKeyguardStatusHeight = 200; 261 // WHEN the position algorithm is run 262 positionClock(); 263 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200); 264 } 265 266 @Test notifPositionAlignedWithClockInSplitShadeMode()267 public void notifPositionAlignedWithClockInSplitShadeMode() { 268 givenLockScreen(); 269 mIsSplitShade = true; 270 mKeyguardStatusHeight = 200; 271 // WHEN the position algorithm is run 272 positionClock(); 273 // THEN the notif padding DOESN'T adjust for keyguard status height. 274 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0); 275 } 276 277 @Test notifPositionAlignedWithClockAndBurnInOffsetInSplitShadeMode()278 public void notifPositionAlignedWithClockAndBurnInOffsetInSplitShadeMode() { 279 setSplitShadeTopMargin(100); // this makes clock to be at 100 280 givenAOD(); 281 mIsSplitShade = true; 282 givenMaxBurnInOffset(100); 283 givenHighestBurnInOffset(); // this makes clock to be at 200 284 // WHEN the position algorithm is run 285 positionClock(); 286 // THEN the notif padding adjusts for burn-in offset: clock position - burn-in offset 287 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(100); 288 } 289 290 @Test clockPositionedDependingOnMarginInSplitShade()291 public void clockPositionedDependingOnMarginInSplitShade() { 292 setSplitShadeTopMargin(400); 293 givenLockScreen(); 294 mIsSplitShade = true; 295 // WHEN the position algorithm is run 296 positionClock(); 297 298 assertThat(mClockPosition.clockY).isEqualTo(400); 299 } 300 301 @Test 302 @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource()303 public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() { 304 int keyguardSplitShadeTopMargin = 100; 305 int largeScreenHeaderHeightResource = 70; 306 when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) 307 .thenReturn(keyguardSplitShadeTopMargin); 308 when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)) 309 .thenReturn(largeScreenHeaderHeightResource); 310 mClockPositionAlgorithm.loadDimens(mContext, mResources); 311 givenLockScreen(); 312 mIsSplitShade = true; 313 // WHEN the position algorithm is run 314 positionClock(); 315 // THEN the notif padding makes up lacking margin (margin - header height). 316 int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightResource; 317 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding); 318 } 319 320 @Test 321 @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper()322 public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() { 323 int keyguardSplitShadeTopMargin = 100; 324 int largeScreenHeaderHeightHelper = 50; 325 int largeScreenHeaderHeightResource = 70; 326 when(LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext)) 327 .thenReturn(largeScreenHeaderHeightHelper); 328 when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) 329 .thenReturn(keyguardSplitShadeTopMargin); 330 when(mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)) 331 .thenReturn(largeScreenHeaderHeightResource); 332 mClockPositionAlgorithm.loadDimens(mContext, mResources); 333 givenLockScreen(); 334 mIsSplitShade = true; 335 // WHEN the position algorithm is run 336 positionClock(); 337 // THEN the notif padding makes up lacking margin (margin - header height). 338 int expectedPadding = keyguardSplitShadeTopMargin - largeScreenHeaderHeightHelper; 339 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(expectedPadding); 340 } 341 342 @Test notifPaddingAccountsForMultiUserSwitcherInSplitShade()343 public void notifPaddingAccountsForMultiUserSwitcherInSplitShade() { 344 setSplitShadeTopMargin(100); 345 mUserSwitchHeight = 150; 346 givenLockScreen(); 347 mIsSplitShade = true; 348 // WHEN the position algorithm is run 349 positionClock(); 350 // THEN the notif padding is split shade top margin + user switch height 351 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(250); 352 } 353 354 @Test clockDoesntAccountForMultiUserSwitcherInSplitShade()355 public void clockDoesntAccountForMultiUserSwitcherInSplitShade() { 356 setSplitShadeTopMargin(100); 357 mUserSwitchHeight = 150; 358 givenLockScreen(); 359 mIsSplitShade = true; 360 // WHEN the position algorithm is run 361 positionClock(); 362 // THEN clockY = split shade top margin 363 assertThat(mClockPosition.clockY).isEqualTo(100); 364 } 365 366 @Test notifPaddingExpandedAlignedWithClockInSplitShadeMode()367 public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() { 368 givenLockScreen(); 369 mIsSplitShade = true; 370 mKeyguardStatusHeight = 200; 371 // WHEN the position algorithm is run 372 positionClock(); 373 // THEN the padding DOESN'T adjust for keyguard status height. 374 assertThat(mClockPosition.stackScrollerPaddingExpanded) 375 .isEqualTo(mClockPosition.clockY); 376 } 377 378 @Test notifPadding_splitShade()379 public void notifPadding_splitShade() { 380 givenLockScreen(); 381 mIsSplitShade = true; 382 mKeyguardStatusHeight = 200; 383 // WHEN the position algorithm is run 384 positionClock(); 385 // THEN the padding DOESN'T adjust for keyguard status height. 386 assertThat(mClockPositionAlgorithm.getLockscreenNotifPadding(/* nsslTop= */ 10)) 387 .isEqualTo(mKeyguardStatusBarHeaderHeight - 10); 388 } 389 390 @Test notifPadding_portraitShade_bypassOff()391 public void notifPadding_portraitShade_bypassOff() { 392 givenLockScreen(); 393 mIsSplitShade = false; 394 mBypassEnabled = false; 395 396 // mMinTopMargin = 100 = 80 + max(20, 0) 397 mKeyguardStatusBarHeaderHeight = 80; 398 mUserSwitchHeight = 20; 399 when(mResources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)) 400 .thenReturn(0); 401 402 mKeyguardStatusHeight = 200; 403 404 // WHEN the position algorithm is run 405 positionClock(); 406 407 // THEN padding = 300 = mMinTopMargin(100) + mKeyguardStatusHeight(200) 408 assertThat(mClockPositionAlgorithm.getLockscreenNotifPadding(/* nsslTop= */ 50)) 409 .isEqualTo(300); 410 } 411 412 @Test notifPadding_portraitShade_bypassOn()413 public void notifPadding_portraitShade_bypassOn() { 414 givenLockScreen(); 415 mIsSplitShade = false; 416 mBypassEnabled = true; 417 mUnlockedStackScrollerPadding = 200; 418 419 // WHEN the position algorithm is run 420 positionClock(); 421 422 // THEN padding = 150 = mUnlockedStackScrollerPadding(200) - nsslTop(50) 423 assertThat(mClockPositionAlgorithm.getLockscreenNotifPadding(/* nsslTop= */ 50)) 424 .isEqualTo(150); 425 } 426 427 @Test notifPositionWithLargeClockOnLockScreen()428 public void notifPositionWithLargeClockOnLockScreen() { 429 // GIVEN on lock screen and clock has a nonzero height 430 givenLockScreen(); 431 mKeyguardStatusHeight = SCREEN_HEIGHT; 432 // WHEN the position algorithm is run 433 positionClock(); 434 // THEN the notif padding is below keyguard status area 435 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(SCREEN_HEIGHT); 436 } 437 438 @Test notifPositionWithFullDragOnLockScreen()439 public void notifPositionWithFullDragOnLockScreen() { 440 // GIVEN the lock screen is dragged up 441 givenLockScreen(); 442 mKeyguardStatusHeight = EMPTY_HEIGHT; 443 mPanelExpansion = 0.f; 444 // WHEN the clock position algorithm is run 445 positionClock(); 446 // THEN the notif padding is zero. 447 assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0); 448 } 449 450 @Test notifPositionWithLargeClockFullDragOnLockScreen()451 public void notifPositionWithLargeClockFullDragOnLockScreen() { 452 // GIVEN the lock screen is dragged up and a full screen clock 453 givenLockScreen(); 454 mKeyguardStatusHeight = SCREEN_HEIGHT; 455 mPanelExpansion = 0.f; 456 // WHEN the clock position algorithm is run 457 positionClock(); 458 assertThat(mClockPosition.stackScrollerPadding).isEqualTo( 459 (int) (mKeyguardStatusHeight * .667f)); 460 } 461 462 @Test clockHiddenWhenQsIsExpanded()463 public void clockHiddenWhenQsIsExpanded() { 464 // GIVEN on the lock screen with visible notifications 465 givenLockScreen(); 466 mQsExpansion = 1; 467 // WHEN the clock position algorithm is run 468 positionClock(); 469 // THEN the clock is transparent. 470 assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT); 471 } 472 473 @Test clockNotHiddenWhenQsIsExpandedInSplitShade()474 public void clockNotHiddenWhenQsIsExpandedInSplitShade() { 475 // GIVEN on the split lock screen with QS expansion 476 givenLockScreen(); 477 mIsSplitShade = true; 478 setSplitShadeTopMargin(100); 479 mQsExpansion = 1; 480 481 // WHEN the clock position algorithm is run 482 positionClock(); 483 484 assertThat(mClockPosition.clockAlpha).isEqualTo(1); 485 } 486 487 @Test clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD()488 public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() { 489 // GIVEN a center aligned clock 490 mClockTopAligned = false; 491 492 // GIVEN the clock + udfps are 100px apart 493 mClockBottom = SCREEN_HEIGHT - 500; 494 mUdfpsTop = SCREEN_HEIGHT - 400; 495 496 // GIVEN it's AOD and the burn-in y value is 200 497 givenAOD(); 498 givenMaxBurnInOffset(200); 499 500 // WHEN the clock position algorithm is run with the highest burn in offset 501 givenHighestBurnInOffset(); 502 positionClock(); 503 504 // THEN the worst-case clock Y position is shifted only by 100 (not the full 200), 505 // so that it's at the same location as mUdfpsTop 506 assertThat(mClockPosition.clockY).isEqualTo(100); 507 508 // WHEN the clock position algorithm is run with the lowest burn in offset 509 givenLowestBurnInOffset(); 510 positionClock(); 511 512 // THEN lowest case starts at 0 513 assertThat(mClockPosition.clockY).isEqualTo(0); 514 } 515 516 @Test clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock()517 public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() { 518 // GIVEN a center aligned clock 519 mClockTopAligned = false; 520 521 // GIVEN there's space at the top of the screen on LS (that's available to be used for 522 // burn-in on AOD) 523 mKeyguardStatusBarHeaderHeight = 150; 524 525 // GIVEN the bottom of the clock is beyond the top of UDFPS 526 mClockBottom = SCREEN_HEIGHT - 300; 527 mUdfpsTop = SCREEN_HEIGHT - 400; 528 529 // GIVEN it's AOD and the burn-in y value is 200 530 givenAOD(); 531 givenMaxBurnInOffset(200); 532 533 // WHEN the clock position algorithm is run with the highest burn in offset 534 givenHighestBurnInOffset(); 535 positionClock(); 536 537 // THEN the algo should shift the clock up and use the area above the clock for 538 // burn-in since the burn in offset > space above clock 539 assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight); 540 541 // WHEN the clock position algorithm is run with the lowest burn in offset 542 givenLowestBurnInOffset(); 543 positionClock(); 544 545 // THEN lowest case starts at mCutoutTopInset (0 in this case) 546 assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset); 547 } 548 549 @Test clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset()550 public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() { 551 // GIVEN a center aligned clock 552 mClockTopAligned = false; 553 554 // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for 555 // burn-in on AOD) but 50px are taken up by the cutout 556 mKeyguardStatusBarHeaderHeight = 200; 557 mCutoutTopInset = 50; 558 559 // GIVEN the bottom of the clock is beyond the top of UDFPS 560 mClockBottom = SCREEN_HEIGHT - 300; 561 mUdfpsTop = SCREEN_HEIGHT - 400; 562 563 // GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock) 564 givenAOD(); 565 int maxYBurnInOffset = 25; 566 givenMaxBurnInOffset(maxYBurnInOffset); 567 568 // WHEN the clock position algorithm is run with the highest burn in offset 569 givenHighestBurnInOffset(); 570 positionClock(); 571 572 // THEN the algo should shift the clock up and use the area above the clock for 573 // burn-in 574 assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight); 575 576 // WHEN the clock position algorithm is run with the lowest burn in offset 577 givenLowestBurnInOffset(); 578 positionClock(); 579 580 // THEN lowest case starts above mKeyguardStatusBarHeaderHeight 581 assertThat(mClockPosition.clockY).isEqualTo( 582 mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset); 583 } 584 585 @Test clockPositionShiftsToMaximizeUdfpsBurnInMovement()586 public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() { 587 // GIVEN a center aligned clock 588 mClockTopAligned = false; 589 590 // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for 591 // burn-in on AOD) but 50px are taken up by the cutout 592 mKeyguardStatusBarHeaderHeight = 200; 593 mCutoutTopInset = 50; 594 int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset; 595 596 // GIVEN the bottom of the clock and the top of UDFPS are 100px apart 597 mClockBottom = SCREEN_HEIGHT - 500; 598 mUdfpsTop = SCREEN_HEIGHT - 400; 599 float lowerSpaceAvailable = mUdfpsTop - mClockBottom; 600 601 // GIVEN it's AOD and the burn-in y value is 200 602 givenAOD(); 603 givenMaxBurnInOffset(200); 604 605 // WHEN the clock position algorithm is run with the highest burn in offset 606 givenHighestBurnInOffset(); 607 positionClock(); 608 609 // THEN the algo should shift the clock up and use both the area above 610 // the clock and below the clock (vertically centered in its allowed area) 611 assertThat(mClockPosition.clockY).isEqualTo( 612 (int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable)); 613 614 // WHEN the clock position algorithm is run with the lowest burn in offset 615 givenLowestBurnInOffset(); 616 positionClock(); 617 618 // THEN lowest case starts at mCutoutTopInset 619 assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset); 620 } 621 setSplitShadeTopMargin(int value)622 private void setSplitShadeTopMargin(int value) { 623 when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) 624 .thenReturn(value); 625 mClockPositionAlgorithm.loadDimens(mContext, mResources); 626 } 627 givenHighestBurnInOffset()628 private void givenHighestBurnInOffset() { 629 when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg()); 630 } 631 givenLowestBurnInOffset()632 private void givenLowestBurnInOffset() { 633 when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0); 634 } 635 givenMaxBurnInOffset(int offset)636 private void givenMaxBurnInOffset(int offset) { 637 when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_clock)) 638 .thenReturn(offset); 639 mClockPositionAlgorithm.loadDimens(mContext, mResources); 640 } 641 givenAOD()642 private void givenAOD() { 643 mPanelExpansion = 1.f; 644 mDark = 1.f; 645 } 646 givenLockScreen()647 private void givenLockScreen() { 648 mPanelExpansion = 1.f; 649 mDark = 0.f; 650 } 651 652 /** 653 * Setup and run the clock position algorithm. 654 * 655 * mClockPosition.clockY will contain the top y-coordinate for the clock position 656 */ positionClock()657 private void positionClock() { 658 mClockPositionAlgorithm.setup( 659 mKeyguardStatusBarHeaderHeight, 660 mPanelExpansion, 661 mKeyguardStatusHeight, 662 mUserSwitchHeight, 663 0 /* userSwitchPreferredY */, 664 mDark, 665 ZERO_DRAG, 666 mBypassEnabled, 667 mUnlockedStackScrollerPadding, 668 mQsExpansion, 669 mCutoutTopInset, 670 mIsSplitShade, 671 mUdfpsTop, 672 mClockBottom, 673 mClockTopAligned); 674 mClockPositionAlgorithm.run(mClockPosition); 675 } 676 } 677