blob: 1827c7f0c0a6855fb4ac6f5d80c75f8d7a3de4fb [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.AdditionalAnswers.returnsFirstArg;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.doze.util.BurnInHelperKt;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final int SCREEN_HEIGHT = 2000;
private static final int EMPTY_HEIGHT = 0;
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
private static final float TRANSPARENT = 0.f;
@Mock
private Resources mResources;
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
private MockitoSession mStaticMockSession;
private float mPanelExpansion;
private int mKeyguardStatusBarHeaderHeight;
private int mKeyguardStatusHeight;
private int mUserSwitchHeight;
private float mDark;
private float mQsExpansion;
private int mCutoutTopInset = 0;
private boolean mIsSplitShade = false;
private float mUdfpsTop = -1;
private float mClockBottom = SCREEN_HEIGHT / 2;
private boolean mClockTopAligned;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mStaticMockSession = mockitoSession()
.mockStatic(BurnInHelperKt.class)
.startMocking();
mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
mClockPositionAlgorithm.loadDimens(mResources);
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
}
@After
public void tearDown() {
mStaticMockSession.finishMocking();
}
@Test
public void clockPositionTopOfScreenOnAOD() {
// GIVEN on AOD and clock has 0 height
givenAOD();
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is the top of the screen
assertThat(mClockPosition.clockY).isEqualTo(0);
// AND the clock is opaque and positioned on the left.
assertThat(mClockPosition.clockX).isEqualTo(0);
assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
public void clockPositionBelowCutout() {
// GIVEN on AOD and clock has 0 height
givenAOD();
mKeyguardStatusHeight = EMPTY_HEIGHT;
mCutoutTopInset = 300;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is below the cutout
assertThat(mClockPosition.clockY).isEqualTo(300);
// AND the clock is opaque and positioned on the left.
assertThat(mClockPosition.clockX).isEqualTo(0);
assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
public void clockPositionAdjustsForKeyguardStatusOnAOD() {
// GIVEN on AOD with a clock of height 100
givenAOD();
mKeyguardStatusHeight = 100;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is at the top
assertThat(mClockPosition.clockY).isEqualTo(0);
// AND the clock is opaque and positioned on the left.
assertThat(mClockPosition.clockX).isEqualTo(0);
assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
public void clockPositionLargeClockOnAOD() {
// GIVEN on AOD with a full screen clock
givenAOD();
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position doesn't overflow the screen.
assertThat(mClockPosition.clockY).isEqualTo(0);
// AND the clock is opaque and positioned on the left.
assertThat(mClockPosition.clockX).isEqualTo(0);
assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
}
@Test
public void clockPositionTopOfScreenOnLockScreen() {
// GIVEN on lock screen with clock of 0 height
givenLockScreen();
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is the top of the screen
assertThat(mClockPosition.clockY).isEqualTo(0);
// AND the clock is positioned on the left.
assertThat(mClockPosition.clockX).isEqualTo(0);
}
@Test
public void clockPositionWithPartialDragOnLockScreen() {
// GIVEN dragging up on lock screen
givenLockScreen();
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.5f;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position adjusts with drag gesture.
assertThat(mClockPosition.clockY).isLessThan(1000);
// AND the clock is positioned on the left and not fully opaque.
assertThat(mClockPosition.clockX).isEqualTo(0);
assertThat(mClockPosition.clockAlpha).isLessThan(OPAQUE);
}
@Test
public void clockPositionWithFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up
givenLockScreen();
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock is transparent.
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
@Test
public void largeClockOnLockScreenIsTransparent() {
// GIVEN on lock screen with a full screen clock
givenLockScreen();
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock is transparent
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
@Test
public void notifPositionTopOfScreenOnAOD() {
// GIVEN on AOD and clock has 0 height
givenAOD();
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding is 0 (top of screen)
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
}
@Test
public void notifPositionIndependentOfKeyguardStatusHeightOnAOD() {
// GIVEN on AOD and clock has a nonzero height
givenAOD();
mKeyguardStatusHeight = 100;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding adjusts for keyguard status height
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(100);
}
@Test
public void notifPositionWithLargeClockOnAOD() {
// GIVEN on AOD and clock has a nonzero height
givenAOD();
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding is, unfortunately, the entire screen.
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(SCREEN_HEIGHT);
}
@Test
public void notifPositionMiddleOfScreenOnLockScreen() {
// GIVEN on lock screen and clock has 0 height
givenLockScreen();
mKeyguardStatusHeight = EMPTY_HEIGHT;
// WHEN the position algorithm is run
positionClock();
// THEN the notif are placed to the top of the screen
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
}
@Test
public void notifPositionAdjustsForClockHeightOnLockScreen() {
// GIVEN on lock screen and stack scroller has a nonzero height
givenLockScreen();
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(200);
}
@Test
public void notifPositionAlignedWithClockInSplitShadeMode() {
givenLockScreen();
mIsSplitShade = true;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding DOESN'T adjust for keyguard status height.
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
}
@Test
public void clockPositionedDependingOnMarginInSplitShade() {
setSplitShadeTopMargin(400);
mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
positionClock();
assertThat(mClockPosition.clockY).isEqualTo(400);
}
@Test
public void notifPaddingMakesUpToFullMarginInSplitShade() {
when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
.thenReturn(100);
when(mResources.getDimensionPixelSize(R.dimen.split_shade_header_height))
.thenReturn(70);
mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding makes up lacking margin (margin - header height = 30).
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(30);
}
@Test
public void notifPaddingAccountsForMultiUserSwitcherInSplitShade() {
setSplitShadeTopMargin(100);
mUserSwitchHeight = 150;
mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding is split shade top margin + user switch height
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(250);
}
@Test
public void clockDoesntAccountForMultiUserSwitcherInSplitShade() {
setSplitShadeTopMargin(100);
mUserSwitchHeight = 150;
mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
positionClock();
// THEN clockY = split shade top margin
assertThat(mClockPosition.clockY).isEqualTo(100);
}
@Test
public void notifPaddingExpandedAlignedWithClockInSplitShadeMode() {
givenLockScreen();
mIsSplitShade = true;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
// THEN the padding DOESN'T adjust for keyguard status height.
assertThat(mClockPosition.stackScrollerPaddingExpanded)
.isEqualTo(mClockPosition.clockY);
}
@Test
public void notifMinPaddingAlignedWithClockInSplitShadeMode() {
givenLockScreen();
mIsSplitShade = true;
mKeyguardStatusHeight = 200;
// WHEN the position algorithm is run
positionClock();
// THEN the padding DOESN'T adjust for keyguard status height.
assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding())
.isEqualTo(mKeyguardStatusBarHeaderHeight);
}
@Test
public void notifPositionWithLargeClockOnLockScreen() {
// GIVEN on lock screen and clock has a nonzero height
givenLockScreen();
mKeyguardStatusHeight = SCREEN_HEIGHT;
// WHEN the position algorithm is run
positionClock();
// THEN the notif padding is below keyguard status area
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(SCREEN_HEIGHT);
}
@Test
public void notifPositionWithFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up
givenLockScreen();
mKeyguardStatusHeight = EMPTY_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
positionClock();
// THEN the notif padding is zero.
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(0);
}
@Test
public void notifPositionWithLargeClockFullDragOnLockScreen() {
// GIVEN the lock screen is dragged up and a full screen clock
givenLockScreen();
mKeyguardStatusHeight = SCREEN_HEIGHT;
mPanelExpansion = 0.f;
// WHEN the clock position algorithm is run
positionClock();
assertThat(mClockPosition.stackScrollerPadding).isEqualTo(
(int) (mKeyguardStatusHeight * .667f));
}
@Test
public void clockHiddenWhenQsIsExpanded() {
// GIVEN on the lock screen with visible notifications
givenLockScreen();
mQsExpansion = 1;
// WHEN the clock position algorithm is run
positionClock();
// THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2).
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
@Test
public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() {
// GIVEN a center aligned clock
mClockTopAligned = false;
// GIVEN the clock + udfps are 100px apart
mClockBottom = SCREEN_HEIGHT - 500;
mUdfpsTop = SCREEN_HEIGHT - 400;
// GIVEN it's AOD and the burn-in y value is 200
givenAOD();
givenMaxBurnInOffset(200);
// WHEN the clock position algorithm is run with the highest burn in offset
givenHighestBurnInOffset();
positionClock();
// THEN the worst-case clock Y position is shifted only by 100 (not the full 200),
// so that it's at the same location as mUdfpsTop
assertThat(mClockPosition.clockY).isEqualTo(100);
// WHEN the clock position algorithm is run with the lowest burn in offset
givenLowestBurnInOffset();
positionClock();
// THEN lowest case starts at 0
assertThat(mClockPosition.clockY).isEqualTo(0);
}
@Test
public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() {
// GIVEN a center aligned clock
mClockTopAligned = false;
// GIVEN there's space at the top of the screen on LS (that's available to be used for
// burn-in on AOD)
mKeyguardStatusBarHeaderHeight = 150;
// GIVEN the bottom of the clock is beyond the top of UDFPS
mClockBottom = SCREEN_HEIGHT - 300;
mUdfpsTop = SCREEN_HEIGHT - 400;
// GIVEN it's AOD and the burn-in y value is 200
givenAOD();
givenMaxBurnInOffset(200);
// WHEN the clock position algorithm is run with the highest burn in offset
givenHighestBurnInOffset();
positionClock();
// THEN the algo should shift the clock up and use the area above the clock for
// burn-in since the burn in offset > space above clock
assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
// WHEN the clock position algorithm is run with the lowest burn in offset
givenLowestBurnInOffset();
positionClock();
// THEN lowest case starts at mCutoutTopInset (0 in this case)
assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
}
@Test
public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() {
// GIVEN a center aligned clock
mClockTopAligned = false;
// GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
// burn-in on AOD) but 50px are taken up by the cutout
mKeyguardStatusBarHeaderHeight = 200;
mCutoutTopInset = 50;
// GIVEN the bottom of the clock is beyond the top of UDFPS
mClockBottom = SCREEN_HEIGHT - 300;
mUdfpsTop = SCREEN_HEIGHT - 400;
// GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock)
givenAOD();
int maxYBurnInOffset = 25;
givenMaxBurnInOffset(maxYBurnInOffset);
// WHEN the clock position algorithm is run with the highest burn in offset
givenHighestBurnInOffset();
positionClock();
// THEN the algo should shift the clock up and use the area above the clock for
// burn-in
assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
// WHEN the clock position algorithm is run with the lowest burn in offset
givenLowestBurnInOffset();
positionClock();
// THEN lowest case starts above mKeyguardStatusBarHeaderHeight
assertThat(mClockPosition.clockY).isEqualTo(
mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset);
}
@Test
public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() {
// GIVEN a center aligned clock
mClockTopAligned = false;
// GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
// burn-in on AOD) but 50px are taken up by the cutout
mKeyguardStatusBarHeaderHeight = 200;
mCutoutTopInset = 50;
int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset;
// GIVEN the bottom of the clock and the top of UDFPS are 100px apart
mClockBottom = SCREEN_HEIGHT - 500;
mUdfpsTop = SCREEN_HEIGHT - 400;
float lowerSpaceAvailable = mUdfpsTop - mClockBottom;
// GIVEN it's AOD and the burn-in y value is 200
givenAOD();
givenMaxBurnInOffset(200);
// WHEN the clock position algorithm is run with the highest burn in offset
givenHighestBurnInOffset();
positionClock();
// THEN the algo should shift the clock up and use both the area above
// the clock and below the clock (vertically centered in its allowed area)
assertThat(mClockPosition.clockY).isEqualTo(
(int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable));
// WHEN the clock position algorithm is run with the lowest burn in offset
givenLowestBurnInOffset();
positionClock();
// THEN lowest case starts at mCutoutTopInset
assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
}
private void setSplitShadeTopMargin(int value) {
when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
.thenReturn(value);
}
private void givenHighestBurnInOffset() {
when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg());
}
private void givenLowestBurnInOffset() {
when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0);
}
private void givenMaxBurnInOffset(int offset) {
when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_clock))
.thenReturn(offset);
mClockPositionAlgorithm.loadDimens(mResources);
}
private void givenAOD() {
mPanelExpansion = 1.f;
mDark = 1.f;
}
private void givenLockScreen() {
mPanelExpansion = 1.f;
mDark = 0.f;
}
/**
* Setup and run the clock position algorithm.
*
* mClockPosition.clockY will contain the top y-coordinate for the clock position
*/
private void positionClock() {
mClockPositionAlgorithm.setup(
mKeyguardStatusBarHeaderHeight,
mPanelExpansion,
mKeyguardStatusHeight,
mUserSwitchHeight,
0 /* userSwitchPreferredY */,
mDark,
ZERO_DRAG,
false /* bypassEnabled */,
0 /* unlockedStackScrollerPadding */,
mQsExpansion,
mCutoutTopInset,
mIsSplitShade,
mUdfpsTop,
mClockBottom,
mClockTopAligned);
mClockPositionAlgorithm.run(mClockPosition);
}
}