Merge "[DO NOT MERGE] Smartspace - Don't create session ahead of init" into sc-qpr1-dev
diff --git a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
index 941df96..aa38000 100644
--- a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
@@ -20,8 +20,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="-105%"
diff --git a/core/res/res/anim-ldrtl/task_close_enter.xml b/core/res/res/anim-ldrtl/task_close_enter.xml
index 1994048..5ace46d 100644
--- a/core/res/res/anim-ldrtl/task_close_enter.xml
+++ b/core/res/res/anim-ldrtl/task_close_enter.xml
@@ -16,8 +16,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="105%"
diff --git a/core/res/res/anim-ldrtl/task_close_exit.xml b/core/res/res/anim-ldrtl/task_close_exit.xml
index 8c0aaa8..76fbdff 100644
--- a/core/res/res/anim-ldrtl/task_close_exit.xml
+++ b/core/res/res/anim-ldrtl/task_close_exit.xml
@@ -16,8 +16,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="0"
diff --git a/core/res/res/anim-ldrtl/task_open_enter.xml b/core/res/res/anim-ldrtl/task_open_enter.xml
index fb7741c..52c74a6 100644
--- a/core/res/res/anim-ldrtl/task_open_enter.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter.xml
@@ -18,8 +18,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="-105%"
diff --git a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
index 69631f6..90ec071 100644
--- a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
@@ -18,8 +18,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="-105%"
diff --git a/core/res/res/anim-ldrtl/task_open_exit.xml b/core/res/res/anim-ldrtl/task_open_exit.xml
index f455334..beb6fca 100644
--- a/core/res/res/anim-ldrtl/task_open_exit.xml
+++ b/core/res/res/anim-ldrtl/task_open_exit.xml
@@ -16,8 +16,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="0"
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
index a495aa1..f6d7b72 100644
--- a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -20,8 +20,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="105%"
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index ec6e03b..52017b1 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -18,8 +18,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="-105%"
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index 4b1e89c..736f3f2 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -18,8 +18,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="0"
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index d538446..3c93438 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -20,8 +20,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="105%"
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
index dc316ff..16249d1 100644
--- a/core/res/res/anim/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -20,8 +20,7 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
      android:zAdjustment="top"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="105%"
diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml
index f8ab655..d170317 100644
--- a/core/res/res/anim/task_open_exit.xml
+++ b/core/res/res/anim/task_open_exit.xml
@@ -18,8 +18,7 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
      android:shareInterpolator="false"
-     android:hasRoundedCorners="true"
-     android:background="@color/overview_background">
+     android:hasRoundedCorners="true">
 
     <translate
         android:fromXDelta="0"
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2764396..ebd559f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4431,4 +4431,6 @@
   <java-symbol type="bool" name="config_volumeShowRemoteSessions" />
 
   <java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
+
+  <java-symbol type="color" name="overview_background"/>
 </resources>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 19ec8ce..f057603 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
 
 <resources>
     <!-- Minimum margin between clock and top of screen or ambient indication -->
-    <dimen name="keyguard_clock_top_margin">76dp</dimen>
+    <dimen name="keyguard_clock_top_margin">38dp</dimen>
 
     <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
     <dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 55f23db..c231afc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -743,9 +743,7 @@
     <!-- The margin between the status view and the notifications on Keyguard.-->
     <dimen name="keyguard_status_view_bottom_margin">20dp</dimen>
     <!-- Minimum margin between clock and status bar -->
-    <dimen name="keyguard_clock_top_margin">36dp</dimen>
-    <!-- The margin between top of clock and bottom of lock icon. -->
-    <dimen name="keyguard_clock_lock_margin">16dp</dimen>
+    <dimen name="keyguard_clock_top_margin">18dp</dimen>
     <!-- The amount to shift the clocks during a small/large transition -->
     <dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
     <!-- When large clock is showing, offset the smartspace by this amount -->
@@ -1149,9 +1147,9 @@
     <dimen name="default_burn_in_prevention_offset">15dp</dimen>
 
     <!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
-         direction that elements aer moved to prevent burn-in on AOD-->
-    <dimen name="udfps_burn_in_offset_x">2dp</dimen>
-    <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+         direction that elements are moved to prevent burn-in on AOD-->
+    <dimen name="udfps_burn_in_offset_x">7px</dimen>
+    <dimen name="udfps_burn_in_offset_y">28px</dimen>
 
     <dimen name="corner_size">8dp</dimen>
     <dimen name="top_padding">0dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d0b2e9f..93d60cc 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -416,7 +416,9 @@
     </style>
 
     <!-- Overridden by values-television/styles.xml with tv-specific settings -->
-    <style name="volume_dialog_theme" parent="Theme.SystemUI"/>
+    <style name="volume_dialog_theme" parent="Theme.SystemUI">
+        <item name="android:windowIsFloating">true</item>
+    </style>
 
     <style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f4a3fb2..464f65b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -70,13 +70,16 @@
      * Clock for both small and large sizes
      */
     private AnimatableClockController mClockViewController;
-    private FrameLayout mClockFrame;
+    private FrameLayout mClockFrame; // top aligned clock
     private AnimatableClockController mLargeClockViewController;
-    private FrameLayout mLargeClockFrame;
+    private FrameLayout mLargeClockFrame; // centered clock
 
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardBypassController mBypassController;
 
+    private int mLargeClockTopMargin = 0;
+    private int mKeyguardClockTopMargin = 0;
+
     /**
      * Listener for changes to the color palette.
      *
@@ -175,6 +178,8 @@
         }
         mColorExtractor.addOnColorsChangedListener(mColorsListener);
         mView.updateColors(getGradientColors());
+        mKeyguardClockTopMargin =
+                mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
         if (mOnlyClock) {
             View ksa = mView.findViewById(R.id.keyguard_status_area);
@@ -249,6 +254,8 @@
      */
     public void onDensityOrFontScaleChanged() {
         mView.onDensityOrFontScaleChanged();
+        mKeyguardClockTopMargin =
+                mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
 
         updateClockLayout();
     }
@@ -257,9 +264,12 @@
         if (mSmartspaceController.isEnabled()) {
             RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
                     MATCH_PARENT);
-            lp.topMargin = getContext().getResources().getDimensionPixelSize(
+            mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize(
                     R.dimen.keyguard_large_clock_top_margin);
+            lp.topMargin = mLargeClockTopMargin;
             mLargeClockFrame.setLayoutParams(lp);
+        } else {
+            mLargeClockTopMargin = 0;
         }
     }
 
@@ -369,6 +379,28 @@
         }
     }
 
+    /**
+     * Get y-bottom position of the currently visible clock on the keyguard.
+     * We can't directly getBottom() because clock changes positions in AOD for burn-in
+     */
+    int getClockBottom(int statusBarHeaderHeight) {
+        if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+            View clock = mLargeClockFrame.findViewById(
+                    com.android.systemui.R.id.animatable_clock_view_large);
+            int frameHeight = mLargeClockFrame.getHeight();
+            int clockHeight = clock.getHeight();
+            return frameHeight / 2 + clockHeight / 2;
+        } else {
+            return mClockFrame.findViewById(
+                    com.android.systemui.R.id.animatable_clock_view).getHeight()
+                    + statusBarHeaderHeight + mKeyguardClockTopMargin;
+        }
+    }
+
+    boolean isClockTopAligned() {
+        return mLargeClockFrame.getVisibility() != View.VISIBLE;
+    }
+
     private void updateAodIcons() {
         NotificationIconContainer nic = (NotificationIconContainer)
                 mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 72e5028..6b3e9c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -185,6 +185,20 @@
     }
 
     /**
+     * Get y-bottom position of the currently visible clock.
+     */
+    public int getClockBottom(int statusBarHeaderHeight) {
+        return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight);
+    }
+
+    /**
+     * @return true if the currently displayed clock is top aligned (as opposed to center aligned)
+     */
+    public boolean isClockTopAligned() {
+        return mKeyguardClockSwitchController.isClockTopAligned();
+    }
+
+    /**
      * Set whether the view accessibility importance mode.
      */
     public void setStatusAccessibilityImportance(int mode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f77c052..b58cab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -83,8 +83,7 @@
     private int mNotificationStackHeight;
 
     /**
-     * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
-     * avatar.
+     * Minimum top margin to avoid overlap with status bar, or multi-user switcher avatar.
      */
     private int mMinTopMargin;
 
@@ -150,6 +149,25 @@
     private boolean mIsSplitShade;
 
     /**
+     * Top location of the udfps icon. This includes the worst case (highest) burn-in
+     * offset that would make the top physically highest on the screen.
+     *
+     * Set to -1 if udfps is not enrolled on the device.
+     */
+    private float mUdfpsTop;
+
+    /**
+     * Bottom y-position of the currently visible clock
+     */
+    private float mClockBottom;
+
+    /**
+     * If true, try to keep clock aligned to the top of the display. Else, assume the clock
+     * is center aligned.
+     */
+    private boolean mIsClockTopAligned;
+
+    /**
      * Refreshes the dimension values.
      */
     public void loadDimens(Resources res) {
@@ -157,7 +175,7 @@
                 R.dimen.keyguard_status_view_bottom_margin);
 
         mContainerTopPadding =
-                res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2;
+                res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
         mBurnInPreventionOffsetX = res.getDimensionPixelSize(
                 R.dimen.burn_in_prevention_offset_x);
         mBurnInPreventionOffsetY = res.getDimensionPixelSize(
@@ -174,7 +192,8 @@
             int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
             boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
             float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
-            float qsExpansion, int cutoutTopInset, boolean isSplitShade) {
+            float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop,
+            float clockBottom, boolean isClockTopAligned) {
         mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
                 userSwitchHeight);
         mMaxShadeBottom = maxShadeBottom;
@@ -193,6 +212,9 @@
         mQsExpansion = qsExpansion;
         mCutoutTopInset = cutoutTopInset;
         mIsSplitShade = isSplitShade;
+        mUdfpsTop = udfpsTop;
+        mClockBottom = clockBottom;
+        mIsClockTopAligned = isClockTopAligned;
     }
 
     public void run(Result result) {
@@ -247,8 +269,34 @@
         if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) {
             shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock);
         }
-        float clockYDark = clockY + burnInPreventionOffsetY() + shift;
 
+        int burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; // requested offset
+        final boolean hasUdfps = mUdfpsTop > -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 (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+                    burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+                }
+                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 (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+                    burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+                }
+                shift = (lowerSpace - upperSpace) / 2;
+            }
+        }
+
+        float clockYDark = clockY
+                + burnInPreventionOffsetY(burnInPreventionOffsetY)
+                + shift;
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
@@ -280,9 +328,7 @@
         return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
     }
 
-    private float burnInPreventionOffsetY() {
-        int offset = mBurnInPreventionOffsetYLargeClock;
-
+    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 551d452..58cbe83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -51,6 +51,7 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -621,6 +622,8 @@
      */
     private float mKeyguardOnlyContentAlpha = 1.0f;
 
+    private float mUdfpsMaxYBurnInOffset;
+
     /**
      * Are we currently in gesture navigation
      */
@@ -957,6 +960,7 @@
         mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources);
         mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
                 R.dimen.notification_side_paddings);
+        mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1301,7 +1305,16 @@
         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);
+            udfpsAodTopLocation = props.sensorLocationY - props.sensorRadius
+                    - mUdfpsMaxYBurnInOffset;
+        }
+
+        mClockPositionAlgorithm.setup(
+                mStatusBarHeaderHeightKeyguard,
                 totalHeight - bottomPadding,
                 mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
                 expandedFraction,
@@ -1313,7 +1326,10 @@
                 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 06b0bb2..16b827b 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 @@
         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 @@
         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 690b841..1043faa 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,24 +16,37 @@
 
 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;
@@ -41,10 +54,15 @@
     private static final boolean HAS_CUSTOM_CLOCK = false;
     private static final boolean HAS_VISIBLE_NOTIFS = false;
 
+    @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 boolean mHasCustomClock;
@@ -52,16 +70,32 @@
     private float mQsExpansion;
     private int mCutoutTopInset = 0; // in pixels
     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();
 
         mHasCustomClock = HAS_CUSTOM_CLOCK;
         mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
     }
 
+    @After
+    public void tearDown() {
+        mStaticMockSession.finishMocking();
+    }
+
     @Test
     public void clockPositionTopOfScreenOnAOD() {
         // GIVEN on AOD and both stack scroll and clock have 0 height
@@ -338,6 +372,155 @@
         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 mCutoutTopInset
+        assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+    }
+
+    @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_large_clock))
+                .thenReturn(offset);
+        mClockPositionAlgorithm.loadDimens(mResources);
+    }
+
     private void givenAOD() {
         mPanelExpansion = 1.f;
         mDark = 1.f;
@@ -348,13 +531,33 @@
         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, SCREEN_HEIGHT, mNotificationStackHeight,
-                mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
-                0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
-                mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
-                0 /* unlockedStackScrollerPadding */, mQsExpansion,
-                mCutoutTopInset, mIsSplitShade);
+        mClockPositionAlgorithm.setup(
+                mKeyguardStatusBarHeaderHeight,
+                SCREEN_HEIGHT,
+                mNotificationStackHeight,
+                mPanelExpansion,
+                SCREEN_HEIGHT,
+                mKeyguardStatusHeight,
+                0 /* userSwitchHeight */,
+                0 /* userSwitchPreferredY */,
+                mHasCustomClock,
+                mHasVisibleNotifs,
+                mDark,
+                ZERO_DRAG,
+                false /* bypassEnabled */,
+                0 /* unlockedStackScrollerPadding */,
+                mQsExpansion,
+                mCutoutTopInset,
+                mIsSplitShade,
+                mUdfpsTop,
+                mClockBottom,
+                mClockTopAligned);
         mClockPositionAlgorithm.run(mClockPosition);
     }
 }