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