From 9b62051f88a70fa8bdd8dfacf747cb05a801359d Mon Sep 17 00:00:00 2001 From: Beverly Date: Fri, 6 Aug 2021 13:34:00 -0400 Subject: Update clock burn-in so it doesn't clash with udpfs * update the UDFPS icon burn-in parameters to be in pixels because we don't want display density to increase the position movement * when the clock is centered aligned, we want to make sure it doesn't overlap with the udfps icon Test: atest SystemUITests, manually update clock times with with DozeUi.BURN_IN_TESTING_ENABLED enabled and see clock doesn't interfere with the UDFPS icon. Fixes: 192423460 Change-Id: I6f95fe3ad763971b804129b517f4c98192ab3674 --- packages/SystemUI/res/values-h800dp/dimens.xml | 2 +- packages/SystemUI/res/values/dimens.xml | 10 +- .../keyguard/KeyguardClockSwitchController.java | 38 +++- .../keyguard/KeyguardStatusViewController.java | 14 ++ .../phone/KeyguardClockPositionAlgorithm.java | 73 ++++++- .../phone/NotificationPanelViewController.java | 23 ++- .../KeyguardClockSwitchControllerTest.java | 2 +- .../phone/KeyguardClockPositionAlgorithmTest.java | 219 ++++++++++++++++++++- 8 files changed, 354 insertions(+), 27 deletions(-) diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index 19ec8cee18a8..f057603e2cd0 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -16,7 +16,7 @@ - 76dp + 38dp 200dp diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c432395139bc..79581e183c4d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -750,9 +750,7 @@ 20dp - 36dp - - 16dp + 18dp 10dp @@ -1157,9 +1155,9 @@ 15dp - 2dp - 8dp + direction that elements are moved to prevent burn-in on AOD--> + 7px + 28px 8dp 0dp diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 11c4b6a27968..260b39378485 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -72,13 +72,16 @@ public class KeyguardClockSwitchController extends ViewController -1; + if (hasUdfps && !mIsClockTopAligned) { + // ensure clock doesn't overlap with the udfps icon + if (mUdfpsTop < mClockBottom) { + // sometimes the clock textView extends beyond udfps, so let's just use the + // space above the KeyguardStatusView/clock as our burn-in offset + burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2; + if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { + burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; + } + shift = -burnInPreventionOffsetY; + } else { + float upperSpace = clockY - mCutoutTopInset; + float lowerSpace = mUdfpsTop - mClockBottom; + // center the burn-in offset within the upper + lower space + burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2; + if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) { + burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; + } + shift = (lowerSpace - upperSpace) / 2; + } + } + + float clockYDark = clockY + + burnInPreventionOffsetY(burnInPreventionOffsetY) + + shift; return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount); } @@ -235,9 +298,7 @@ public class KeyguardClockPositionAlgorithm { return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount); } - private float burnInPreventionOffsetY() { - int offset = mBurnInPreventionOffsetYClock; - + private float burnInPreventionOffsetY(int offset) { return getBurnInOffset(offset * 2, false /* xAxis */) - offset; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 198ad98b8a37..18648b0bd1de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -57,6 +57,9 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; +import android.hardware.biometrics.BiometricSourceType; +import android.hardware.biometrics.SensorLocationInternal; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; @@ -568,6 +571,8 @@ public class NotificationPanelViewController extends PanelViewController { */ private float mKeyguardOnlyContentAlpha = 1.0f; + private float mUdfpsMaxYBurnInOffset; + /** * Are we currently in gesture navigation */ @@ -907,6 +912,7 @@ public class NotificationPanelViewController extends PanelViewController { mView.getContext()); mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( R.dimen.notification_side_paddings); + mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, @@ -1265,7 +1271,17 @@ public class NotificationPanelViewController extends PanelViewController { float darkamount = mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() ? 1.0f : mInterpolatedDarkAmount; - mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard, + + float udfpsAodTopLocation = -1f; + if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) { + FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0); + final SensorLocationInternal location = props.getLocation(); + udfpsAodTopLocation = location.sensorLocationY - location.sensorRadius + - mUdfpsMaxYBurnInOffset; + } + + mClockPositionAlgorithm.setup( + mStatusBarHeaderHeightKeyguard, expandedFraction, mKeyguardStatusViewController.getLockscreenHeight(), userIconHeight, @@ -1274,7 +1290,10 @@ public class NotificationPanelViewController extends PanelViewController { bypassEnabled, getUnlockedStackScrollerPadding(), computeQsExpansionFraction(), mDisplayTopInset, - mShouldUseSplitNotificationShade); + mShouldUseSplitNotificationShade, + udfpsAodTopLocation, + mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), + mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = animate || mAnimateNextPositionUpdate; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 8d615f050af9..88b596c26c0f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -119,6 +119,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mNotificationIcons.getLayoutParams()).thenReturn( mock(RelativeLayout.LayoutParams.class)); when(mView.getContext()).thenReturn(getContext()); + when(mView.getResources()).thenReturn(mResources); when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView); when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView); @@ -127,7 +128,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mLargeClockView.getContext()).thenReturn(getContext()); when(mView.isAttachedToWindow()).thenReturn(true); - when(mResources.getString(anyInt())).thenReturn("h:mm"); when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView); mController = new KeyguardClockSwitchController( mView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index f34f21bde803..d098e1a0b8a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -16,42 +16,80 @@ 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_MARGIN = 0; 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 int mNotificationStackHeight; + private float mPanelExpansion; + private int mKeyguardStatusBarHeaderHeight; private int mKeyguardStatusHeight; private float mDark; private float mQsExpansion; - private int mCutoutTopInsetPx = 0; + 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 @@ -71,7 +109,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { // GIVEN on AOD and clock has 0 height givenAOD(); mKeyguardStatusHeight = EMPTY_HEIGHT; - mCutoutTopInsetPx = 300; + mCutoutTopInset = 300; // WHEN the clock position algorithm is run positionClock(); // THEN the clock Y position is below the cutout @@ -271,6 +309,155 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { 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 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; @@ -281,12 +468,28 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { 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(EMPTY_MARGIN, mPanelExpansion, mKeyguardStatusHeight, - 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */, - mDark, ZERO_DRAG, false /* bypassEnabled */, - 0 /* unlockedStackScrollerPadding */, mQsExpansion, - mCutoutTopInsetPx, mIsSplitShade); + mClockPositionAlgorithm.setup( + mKeyguardStatusBarHeaderHeight, + mPanelExpansion, + mKeyguardStatusHeight, + 0 /* userSwitchHeight */, + 0 /* userSwitchPreferredY */, + mDark, + ZERO_DRAG, + false /* bypassEnabled */, + 0 /* unlockedStackScrollerPadding */, + mQsExpansion, + mCutoutTopInset, + mIsSplitShade, + mUdfpsTop, + mClockBottom, + mClockTopAligned); mClockPositionAlgorithm.run(mClockPosition); } } -- cgit v1.2.3-59-g8ed1b