UI fixes for the progress bar and handle display density changes

Summary of changes:
  - Change the code to use the sensor radius from fingerprintManager for the length of the progress bar
  - Use length and thickness to describe the progress bar instead of height and width.
  - The progress bar overlay is now fullscreen with the progress bar being a child element with fixed width and height. In previous setup, progress bar either kept getting clipped or the length changes were not getting reflected correctly.
  - Update the view leftTop point calculation in the view model to take into account the thickness of the progress bar and the padding from screen edge
  - Added a command to make the progress bar testing easier.

Bug: 305271359
Flag: LEGACY REST_TO_UNLOCK DEVELOPMENT
Test: tested with various display sizes in the settings and different orientations to make sure the progress is aligned next to the sensor.
Test: atest SideFpsSensorInteractorTest
Test: pending for the viewModel, will add in a follow-up CL
Change-Id: I1dd485a96c1c878329e9273f0d102ca009a3fdcd
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
index 183f0e5..9d74677 100644
--- a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
@@ -15,18 +15,18 @@
   ~
   -->
 
-<LinearLayout android:layout_height="match_parent"
+<RelativeLayout
     android:layout_width="match_parent"
-    android:orientation="vertical"
-    android:layoutDirection="ltr"
-    android:gravity="center"
+    android:layout_height="match_parent"
+    android:gravity="left|top"
+    android:background="@android:color/transparent"
     xmlns:android="http://schemas.android.com/apk/res/android">
     <ProgressBar
         android:id="@+id/side_fps_progress_bar"
-        android:layout_width="55dp"
-        android:layout_height="10dp"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/sfps_progress_bar_thickness"
         android:indeterminateOnly="false"
         android:min="0"
         android:max="100"
         android:progressDrawable="@drawable/progress_bar" />
-</LinearLayout>
+</RelativeLayout>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index d1067a9..0628c3e 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -157,4 +157,11 @@
     <dimen name="weather_clock_smartspace_translateX">0dp</dimen>
     <dimen name="weather_clock_smartspace_translateY">0dp</dimen>
 
+    <!-- Additional length to add to the SFPS sensor length we get from framework so that the length
+     of the progress bar matches the length of the power button  -->
+    <dimen name="sfps_progress_bar_length_extra_padding">12dp</dimen>
+    <!-- Thickness of the progress bar we show for the SFPS based authentication. -->
+    <dimen name="sfps_progress_bar_thickness">6dp</dimen>
+    <!-- Padding from the edge of the screen for the progress bar -->
+    <dimen name="sfps_progress_bar_padding_from_edge">7dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index 0567ea2..2bb19cd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -34,9 +34,11 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 @SysUISingleton
 class SideFpsSensorInteractor
@@ -51,7 +53,7 @@
     private val logger: SideFpsLogger,
 ) {
 
-    private val sensorForCurrentDisplay =
+    private val sensorLocationForCurrentDisplay =
         combine(
                 displayStateInteractor.displayChanges,
                 fingerprintPropertyRepository.sensorLocations,
@@ -77,77 +79,89 @@
                 isAvailable,
                 fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
             ) { sfpsAvailable, isSettingEnabled ->
-                logger.logStateChange(sfpsAvailable, isSettingEnabled)
                 sfpsAvailable && isSettingEnabled
             }
         }
 
     val sensorLocation: Flow<SideFpsSensorLocation> =
-        combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map {
-            (rotation, sensorLocation: SensorLocationInternal) ->
-            val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
-            // device dimensions in the current rotation
-            val size = windowManager.maximumWindowMetrics.bounds
-            val isDefaultOrientation = rotation.isDefaultOrientation()
-            // Width and height are flipped is device is not in rotation_0 or rotation_180
-            // Flipping it to the width and height of the device in default orientation.
-            val displayWidth = if (isDefaultOrientation) size.width() else size.height()
-            val displayHeight = if (isDefaultOrientation) size.height() else size.width()
-            val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0
+        combine(displayStateInteractor.currentRotation, sensorLocationForCurrentDisplay, ::Pair)
+            .map { (rotation, sensorLocation: SensorLocationInternal) ->
+                val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0
+                // device dimensions in the current rotation
+                val windowMetrics = windowManager.maximumWindowMetrics
+                val size = windowMetrics.bounds
+                val isDefaultOrientation = rotation.isDefaultOrientation()
+                // Width and height are flipped is device is not in rotation_0 or rotation_180
+                // Flipping it to the width and height of the device in default orientation.
+                val displayWidth = if (isDefaultOrientation) size.width() else size.height()
+                val displayHeight = if (isDefaultOrientation) size.height() else size.width()
+                val sensorLengthInPx = sensorLocation.sensorRadius * 2
 
-            val (sensorLeft, sensorTop) =
-                if (isSensorVerticalInDefaultOrientation) {
-                    when (rotation) {
-                        DisplayRotation.ROTATION_0 -> {
-                            Pair(displayWidth, sensorLocation.sensorLocationY)
+                val (sensorLeft, sensorTop) =
+                    if (isSensorVerticalInDefaultOrientation) {
+                        when (rotation) {
+                            DisplayRotation.ROTATION_0 -> {
+                                Pair(displayWidth, sensorLocation.sensorLocationY)
+                            }
+                            DisplayRotation.ROTATION_90 -> {
+                                Pair(sensorLocation.sensorLocationY, 0)
+                            }
+                            DisplayRotation.ROTATION_180 -> {
+                                Pair(
+                                    0,
+                                    displayHeight -
+                                        sensorLocation.sensorLocationY -
+                                        sensorLengthInPx
+                                )
+                            }
+                            DisplayRotation.ROTATION_270 -> {
+                                Pair(
+                                    displayHeight -
+                                        sensorLocation.sensorLocationY -
+                                        sensorLengthInPx,
+                                    displayWidth
+                                )
+                            }
                         }
-                        DisplayRotation.ROTATION_90 -> {
-                            Pair(sensorLocation.sensorLocationY, 0)
-                        }
-                        DisplayRotation.ROTATION_180 -> {
-                            Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth)
-                        }
-                        DisplayRotation.ROTATION_270 -> {
-                            Pair(
-                                displayHeight - sensorLocation.sensorLocationY - sensorWidth,
-                                displayWidth
-                            )
+                    } else {
+                        when (rotation) {
+                            DisplayRotation.ROTATION_0 -> {
+                                Pair(sensorLocation.sensorLocationX, 0)
+                            }
+                            DisplayRotation.ROTATION_90 -> {
+                                Pair(
+                                    0,
+                                    displayWidth - sensorLocation.sensorLocationX - sensorLengthInPx
+                                )
+                            }
+                            DisplayRotation.ROTATION_180 -> {
+                                Pair(
+                                    displayWidth -
+                                        sensorLocation.sensorLocationX -
+                                        sensorLengthInPx,
+                                    displayHeight
+                                )
+                            }
+                            DisplayRotation.ROTATION_270 -> {
+                                Pair(displayHeight, sensorLocation.sensorLocationX)
+                            }
                         }
                     }
-                } else {
-                    when (rotation) {
-                        DisplayRotation.ROTATION_0 -> {
-                            Pair(sensorLocation.sensorLocationX, 0)
-                        }
-                        DisplayRotation.ROTATION_90 -> {
-                            Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth)
-                        }
-                        DisplayRotation.ROTATION_180 -> {
-                            Pair(
-                                displayWidth - sensorLocation.sensorLocationX - sensorWidth,
-                                displayHeight
-                            )
-                        }
-                        DisplayRotation.ROTATION_270 -> {
-                            Pair(displayHeight, sensorLocation.sensorLocationX)
-                        }
-                    }
-                }
 
-            logger.sensorLocationStateChanged(
-                size,
-                rotation,
-                displayWidth,
-                displayHeight,
-                sensorWidth,
-                isSensorVerticalInDefaultOrientation
-            )
-
-            SideFpsSensorLocation(
-                left = sensorLeft,
-                top = sensorTop,
-                width = sensorWidth,
-                isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
-            )
-        }
+                SideFpsSensorLocation(
+                    left = sensorLeft,
+                    top = sensorTop,
+                    length = sensorLengthInPx,
+                    isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
+                )
+            }
+            .distinctUntilChanged()
+            .onEach {
+                logger.sensorLocationStateChanged(
+                    it.left,
+                    it.top,
+                    it.length,
+                    it.isSensorVerticalInDefaultOrientation
+                )
+            }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
index 35f8e3b..12f374f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt
@@ -21,8 +21,8 @@
     val left: Int,
     /** Pixel offset from the top of the screen */
     val top: Int,
-    /** Width in pixels of the SFPS sensor */
-    val width: Int,
+    /** Length of the SFPS sensor in pixels in current display density */
+    val length: Int,
     /**
      * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or
      * Rotation_180)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
index 1acea5c..ba4876f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -16,19 +16,27 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.animation.ValueAnimator
+import android.graphics.Point
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.log.SideFpsLogger
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
 import com.android.systemui.util.kotlin.Quint
+import java.io.PrintWriter
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
+private const val spfsProgressBarCommand = "sfps-progress-bar"
+
 @SysUISingleton
 class SideFpsProgressBarViewBinder
 @Inject
@@ -37,38 +45,112 @@
     private val view: SideFpsProgressBar,
     @Application private val applicationScope: CoroutineScope,
     private val sfpsController: dagger.Lazy<SideFpsController>,
+    private val logger: SideFpsLogger,
+    private val commandRegistry: CommandRegistry,
 ) : CoreStartable {
 
     override fun start() {
+        commandRegistry.registerCommand(spfsProgressBarCommand) { SfpsProgressBarCommand() }
         applicationScope.launch {
             viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+                logger.isProlongedTouchRequiredForAuthenticationChanged(enabled)
                 if (enabled) {
                     launch {
                         combine(
                                 viewModel.isVisible,
-                                viewModel.sensorLocation,
-                                viewModel.shouldRotate90Degrees,
+                                viewModel.progressBarLocation,
+                                viewModel.rotation,
                                 viewModel.isFingerprintAuthRunning,
-                                viewModel.sensorWidth,
+                                viewModel.progressBarLength,
                                 ::Quint
                             )
-                            .collectLatest {
-                                (visible, location, shouldRotate, fpDetectRunning, sensorWidth) ->
-                                view.updateView(visible, location, shouldRotate, sensorWidth)
-                                // We have to hide the SFPS indicator as the progress bar will
-                                // be shown at the same location
-                                if (visible) {
-                                    sfpsController.get().hideIndicator()
-                                } else if (fpDetectRunning) {
-                                    sfpsController.get().showIndicator()
-                                }
+                            .collectLatest { (visible, location, rotation, fpDetectRunning, length)
+                                ->
+                                updateView(
+                                    visible,
+                                    location,
+                                    fpDetectRunning,
+                                    length,
+                                    viewModel.progressBarThickness,
+                                    rotation,
+                                )
                             }
                     }
                     launch { viewModel.progress.collectLatest { view.setProgress(it) } }
                 } else {
-                    view.hideOverlay()
+                    view.hide()
                 }
             }
         }
     }
+
+    private fun updateView(
+        visible: Boolean,
+        location: Point,
+        fpDetectRunning: Boolean,
+        length: Int,
+        thickness: Int,
+        rotation: Float,
+    ) {
+        logger.sfpsProgressBarStateChanged(visible, location, fpDetectRunning, length, rotation)
+        view.updateView(visible, location, length, thickness, rotation)
+        // We have to hide the SFPS indicator as the progress bar will
+        // be shown at the same location
+        if (visible) {
+            logger.hidingSfpsIndicator()
+            sfpsController.get().hideIndicator()
+        } else if (fpDetectRunning) {
+            logger.showingSfpsIndicator()
+            sfpsController.get().showIndicator()
+        }
+    }
+
+    inner class SfpsProgressBarCommand : Command {
+        private var animator: ValueAnimator? = null
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            if (args.isEmpty() || args[0] == "show" && args.size != 6) {
+                pw.println("invalid command")
+                help(pw)
+            } else {
+                when (args[0]) {
+                    "show" -> {
+                        animator?.cancel()
+                        updateView(
+                            visible = true,
+                            location = Point(Integer.parseInt(args[1]), Integer.parseInt(args[2])),
+                            fpDetectRunning = true,
+                            length = Integer.parseInt(args[3]),
+                            thickness = Integer.parseInt(args[4]),
+                            rotation = Integer.parseInt(args[5]).toFloat(),
+                        )
+                        animator =
+                            ValueAnimator.ofFloat(0.0f, 1.0f).apply {
+                                repeatMode = ValueAnimator.REVERSE
+                                repeatCount = ValueAnimator.INFINITE
+                                addUpdateListener { view.setProgress(it.animatedValue as Float) }
+                            }
+                        animator?.start()
+                    }
+                    "hide" -> {
+                        animator?.cancel()
+                        updateView(
+                            visible = false,
+                            location = Point(0, 0),
+                            fpDetectRunning = false,
+                            length = 0,
+                            thickness = 0,
+                            rotation = 0.0f,
+                        )
+                    }
+                }
+            }
+        }
+
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: adb shell cmd statusbar $spfsProgressBarCommand <command>")
+            pw.println("Available commands:")
+            pw.println("  show x y width height rotation")
+            pw.println("  hide")
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index f7ab1ee..853f176 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -22,17 +22,16 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.WindowManager
 import android.widget.ProgressBar
-import com.android.systemui.biometrics.Utils
+import androidx.core.view.isGone
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import javax.inject.Inject
 
 private const val TAG = "SideFpsProgressBar"
 
-const val progressBarHeight = 100
-
 @SysUISingleton
 class SideFpsProgressBar
 @Inject
@@ -40,31 +39,36 @@
     private val layoutInflater: LayoutInflater,
     private val windowManager: WindowManager,
 ) {
-    private var progressBarWidth = 200
+    private var overlayView: View? = null
+
     fun updateView(
         visible: Boolean,
-        location: Point,
-        shouldRotate90Degrees: Boolean,
-        progressBarWidth: Int
+        viewLeftTopLocation: Point,
+        progressBarWidth: Int,
+        progressBarHeight: Int,
+        rotation: Float,
     ) {
         if (visible) {
-            this.progressBarWidth = progressBarWidth
-            createAndShowOverlay(location, shouldRotate90Degrees)
+            createAndShowOverlay(viewLeftTopLocation, rotation, progressBarWidth, progressBarHeight)
         } else {
-            hideOverlay()
+            hide()
         }
     }
 
-    fun hideOverlay() {
-        overlayView = null
+    fun hide() {
+        progressBar?.isGone = true
     }
 
     private val overlayViewParams =
         WindowManager.LayoutParams(
-                progressBarHeight,
-                progressBarWidth,
+                // overlay is always full screen
+                MATCH_PARENT,
+                MATCH_PARENT,
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
+                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
                 PixelFormat.TRANSPARENT
             )
             .apply {
@@ -78,37 +82,31 @@
                         WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
             }
 
-    private var overlayView: View? = null
-        set(value) {
-            field?.let { oldView -> windowManager.removeView(oldView) }
-            field = value
-            field?.let { newView -> windowManager.addView(newView, overlayViewParams) }
-        }
-
     private fun createAndShowOverlay(
-        fingerprintSensorLocation: Point,
-        shouldRotate90Degrees: Boolean
+        viewLeftTop: Point,
+        rotation: Float,
+        progressBarLength: Int,
+        progressBarThickness: Int,
     ) {
         if (overlayView == null) {
             overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+            windowManager.addView(overlayView, overlayViewParams)
+            progressBar?.pivotX = 0.0f
+            progressBar?.pivotY = 0.0f
         }
-        overlayViewParams.x = fingerprintSensorLocation.x
-        overlayViewParams.y = fingerprintSensorLocation.y
-        if (shouldRotate90Degrees) {
-            overlayView?.rotation = 270.0f
-            overlayViewParams.width = progressBarHeight
-            overlayViewParams.height = progressBarWidth
-        } else {
-            overlayView?.rotation = 0.0f
-            overlayViewParams.width = progressBarWidth
-            overlayViewParams.height = progressBarHeight
-        }
-        windowManager.updateViewLayout(overlayView, overlayViewParams)
+        progressBar?.layoutParams?.width = progressBarLength
+        progressBar?.layoutParams?.height = progressBarThickness
+        progressBar?.translationX = viewLeftTop.x.toFloat()
+        progressBar?.translationY = viewLeftTop.y.toFloat()
+        progressBar?.rotation = rotation
+        progressBar?.isGone = false
+        overlayView?.requestLayout()
     }
 
     fun setProgress(value: Float) {
-        overlayView
-            ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
-            ?.setProgress((value * 100).toInt(), false)
+        progressBar?.setProgress((value * 100).toInt(), false)
     }
+
+    private val progressBar: ProgressBar?
+        get() = overlayView?.findViewById(R.id.side_fps_progress_bar)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 2c3b431..f8996b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.animation.ValueAnimator
+import android.content.Context
 import android.graphics.Point
 import androidx.core.animation.doOnEnd
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +31,7 @@
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -36,6 +39,7 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
@@ -43,6 +47,7 @@
 class SideFpsProgressBarViewModel
 @Inject
 constructor(
+    private val context: Context,
     private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
     displayStateInteractor: DisplayStateInteractor,
@@ -57,24 +62,84 @@
         _progress.value = 0.0f
     }
 
+    private val additionalSensorLengthPadding =
+        context.resources.getDimension(R.dimen.sfps_progress_bar_length_extra_padding).toInt()
+
     val isVisible: Flow<Boolean> = _visible.asStateFlow()
 
     val progress: Flow<Float> = _progress.asStateFlow()
 
-    val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+    val progressBarLength: Flow<Int> =
+        sfpsSensorInteractor.sensorLocation
+            .map { it.length + additionalSensorLengthPadding }
+            .distinctUntilChanged()
 
-    val sensorLocation: Flow<Point> =
-        sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+    val progressBarThickness =
+        context.resources.getDimension(R.dimen.sfps_progress_bar_thickness).toInt()
+
+    val progressBarLocation =
+        combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+            .map { (rotation, sensorLocation) ->
+                val paddingFromEdge =
+                    context.resources
+                        .getDimension(R.dimen.sfps_progress_bar_padding_from_edge)
+                        .toInt()
+                val lengthOfTheProgressBar = sensorLocation.length + additionalSensorLengthPadding
+                val viewLeftTop = Point(sensorLocation.left, sensorLocation.top)
+                val totalDistanceFromTheEdge = paddingFromEdge + progressBarThickness
+
+                val isSensorVerticalNow =
+                    sensorLocation.isSensorVerticalInDefaultOrientation ==
+                        rotation.isDefaultOrientation()
+                if (isSensorVerticalNow) {
+                    // Sensor is vertical to the current orientation, we rotate it 270 deg
+                    // around the (left,top) point as the pivot. We need to push it down the
+                    // length of the progress bar so that it is still aligned to the sensor
+                    viewLeftTop.y += lengthOfTheProgressBar
+                    val isSensorOnTheNearEdge =
+                        rotation == DisplayRotation.ROTATION_180 ||
+                            rotation == DisplayRotation.ROTATION_90
+                    if (isSensorOnTheNearEdge) {
+                        // Add just the padding from the edge to push the progress bar right
+                        viewLeftTop.x += paddingFromEdge
+                    } else {
+                        // View left top is pushed left from the edge by the progress bar thickness
+                        // and the padding.
+                        viewLeftTop.x -= totalDistanceFromTheEdge
+                    }
+                } else {
+                    // Sensor is horizontal to the current orientation.
+                    val isSensorOnTheNearEdge =
+                        rotation == DisplayRotation.ROTATION_0 ||
+                            rotation == DisplayRotation.ROTATION_90
+                    if (isSensorOnTheNearEdge) {
+                        // Add just the padding from the edge to push the progress bar down
+                        viewLeftTop.y += paddingFromEdge
+                    } else {
+                        // Sensor is now at the bottom edge of the device in the current rotation.
+                        // We want to push it up from the bottom edge by the padding and
+                        // the thickness of the progressbar.
+                        viewLeftTop.y -= totalDistanceFromTheEdge
+                        viewLeftTop.x -= additionalSensorLengthPadding
+                    }
+                }
+                viewLeftTop
+            }
 
     val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
 
-    val shouldRotate90Degrees: Flow<Boolean> =
+    val rotation: Flow<Float> =
         combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
             .map { (rotation, sensorLocation) ->
-                if (rotation.isDefaultOrientation()) {
-                    sensorLocation.isSensorVerticalInDefaultOrientation
+                if (
+                    rotation.isDefaultOrientation() ==
+                        sensorLocation.isSensorVerticalInDefaultOrientation
+                ) {
+                    // We should rotate the progress bar 270 degrees in the clockwise direction with
+                    // the left top point as the pivot so that it fills up from bottom to top
+                    270.0f
                 } else {
-                    !sensorLocation.isSensorVerticalInDefaultOrientation
+                    0.0f
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 74923ee..919072a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.log
 
 import android.graphics.Point
-import android.graphics.Rect
-import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.BouncerLog
@@ -40,9 +38,9 @@
     fun sfpsProgressBarStateChanged(
         visible: Boolean,
         location: Point,
-        shouldRotate: Boolean,
         fpDetectRunning: Boolean,
-        sensorWidth: Int
+        sensorWidth: Int,
+        rotation: Float,
     ) {
         buffer.log(
             TAG,
@@ -51,14 +49,14 @@
                 bool1 = visible
                 int1 = location.x
                 int2 = location.y
-                bool2 = shouldRotate
+                str1 = "$rotation"
                 bool3 = fpDetectRunning
                 long1 = sensorWidth.toLong()
             },
             {
                 "SFPS progress bar state changed: visible: $bool1, " +
                     "sensorLocation (x, y): ($int1, $int2), " +
-                    "shouldRotate = $bool2, " +
+                    "rotation = $str1, " +
                     "fpDetectRunning: $bool3, " +
                     "sensorWidth: $long1"
             }
@@ -87,44 +85,25 @@
         )
     }
 
-    fun logStateChange(sfpsAvailable: Boolean, settingEnabled: Boolean) {
-        buffer.log(
-            TAG,
-            LogLevel.DEBUG,
-            {
-                bool1 = sfpsAvailable
-                bool2 = settingEnabled
-            },
-            { "SFPS rest to unlock state changed: sfpsAvailable: $bool1, settingEnabled: $bool2" }
-        )
-    }
-
     fun sensorLocationStateChanged(
-        windowSize: Rect?,
-        rotation: DisplayRotation,
-        displayWidth: Int,
-        displayHeight: Int,
-        sensorWidth: Int,
-        sensorVerticalInDefaultOrientation: Boolean
+        pointOnScreenX: Int,
+        pointOnScreenY: Int,
+        sensorLength: Int,
+        isSensorVerticalInDefaultOrientation: Boolean
     ) {
         buffer.log(
             TAG,
             LogLevel.DEBUG,
             {
-                str1 = "$windowSize"
-                str2 = rotation.name
-                int1 = displayWidth
-                int2 = displayHeight
-                long1 = sensorWidth.toLong()
-                bool1 = sensorVerticalInDefaultOrientation
+                int1 = pointOnScreenX
+                int2 = pointOnScreenY
+                str2 = "$sensorLength"
+                bool1 = isSensorVerticalInDefaultOrientation
             },
             {
-                "sensorLocation state changed: " +
-                    "windowSize: $str1, " +
-                    "rotation: $str2, " +
-                    "widthInRotation0: $int1, " +
-                    "heightInRotation0: $int2, " +
-                    "sensorWidth: $long1, " +
+                "SideFpsSensorLocation state changed: " +
+                    "pointOnScreen: ($int1, $int2), " +
+                    "sensorLength: $str2, " +
                     "sensorVerticalInDefaultOrientation: $bool1"
             }
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 1e7a3d3..3fbdeec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -165,7 +165,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(1000)
             assertThat(sensorLocation!!.top).isEqualTo(200)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -193,7 +193,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(500)
             assertThat(sensorLocation!!.top).isEqualTo(1000)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -221,7 +221,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(200)
             assertThat(sensorLocation!!.top).isEqualTo(0)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -274,7 +274,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(500)
             assertThat(sensorLocation!!.top).isEqualTo(0)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -301,7 +301,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(0)
             assertThat(sensorLocation!!.top).isEqualTo(400)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -328,7 +328,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(400)
             assertThat(sensorLocation!!.top).isEqualTo(800)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -355,7 +355,7 @@
             assertThat(sensorLocation!!.left).isEqualTo(800)
             assertThat(sensorLocation!!.top).isEqualTo(500)
             assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false)
-            assertThat(sensorLocation!!.width).isEqualTo(100)
+            assertThat(sensorLocation!!.length).isEqualTo(100)
         }
 
     @Test
@@ -381,10 +381,14 @@
         rotation: DisplayRotation,
         sensorWidth: Int
     ) {
-        overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth)
         setupDisplayDimensions(width, height)
         currentRotation.value = rotation
-        setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display")
+        setupFingerprint(
+            x = sensorLocationX,
+            y = sensorLocationY,
+            displayId = "expanded_display",
+            sensorRadius = sensorWidth / 2
+        )
     }
 
     private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) {
@@ -392,7 +396,7 @@
             .thenReturn(
                 WindowMetrics(
                     Rect(0, 0, displayWidth, displayHeight),
-                    mock(WindowInsets::class.java)
+                    mock(WindowInsets::class.java),
                 )
             )
     }
@@ -401,7 +405,8 @@
         fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON,
         x: Int = 0,
         y: Int = 0,
-        displayId: String = "display_id_1"
+        displayId: String = "display_id_1",
+        sensorRadius: Int = 150
     ) {
         contextDisplayInfo.uniqueId = displayId
         fingerprintRepository.setProperties(
@@ -415,14 +420,14 @@
                             "someOtherDisplayId",
                             x + 100,
                             y + 100,
-                            0,
+                            sensorRadius,
                         ),
                     displayId to
                         SensorLocationInternal(
                             displayId,
                             x,
                             y,
-                            0,
+                            sensorRadius,
                         )
                 )
         )