Add SideFpsProgressBar to show a progress bar when rest_to_unlock feature is enabled
Bug: 277165756
Test: atest
Change-Id: Iba751dd1bdb137687bc4a368a9b1cddd67f9790f
diff --git a/packages/SystemUI/res-keyguard/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
new file mode 100644
index 0000000..910a74a
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:paddingMode="stack">
+ <item
+ android:id="@android:id/background"
+ android:gravity="center_vertical|fill_horizontal">
+ <shape
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+ </shape>
+ </item>
+ <item
+ android:id="@android:id/progress"
+ android:gravity="center_vertical|fill_horizontal">
+ <clip>
+ <shape
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="?androidprv:attr/textColorPrimary" />
+ </shape>
+ </clip>
+ </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
new file mode 100644
index 0000000..183f0e5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ ~
+ -->
+
+<LinearLayout android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:layoutDirection="ltr"
+ android:gravity="center"
+ 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:indeterminateOnly="false"
+ android:min="0"
+ android:max="100"
+ android:progressDrawable="@drawable/progress_bar" />
+</LinearLayout>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index b7bb35e..84e06e2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -153,7 +153,6 @@
import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -177,6 +176,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -1984,6 +1984,7 @@
@Override
public void onAuthenticationAcquired(int acquireInfo) {
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired");
+ mLogger.logFingerprintAcquired(acquireInfo);
handleFingerprintAcquired(acquireInfo);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index fe19616..fa07072 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -44,6 +44,7 @@
import javax.inject.Inject
private const val TAG = "KeyguardUpdateMonitorLog"
+private const val FP_LOG_TAG = "KeyguardFingerprintLog"
/** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */
class KeyguardUpdateMonitorLogger
@@ -157,7 +158,7 @@
fun logFingerprintAuthForWrongUser(authUserId: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = authUserId },
{ "Fingerprint authenticated for wrong user: $int1" }
@@ -166,7 +167,7 @@
fun logFingerprintDisabledForUser(userId: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = userId },
{ "Fingerprint disabled by DPM for userId: $int1" }
@@ -174,12 +175,17 @@
}
fun logFingerprintLockoutReset(@LockoutMode mode: Int) {
- logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" })
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ { int1 = mode },
+ { "handleFingerprintLockoutReset: $int1" }
+ )
}
fun logFingerprintRunningState(fingerprintRunningState: Int) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{ int1 = fingerprintRunningState },
{ "fingerprintRunningState: $int1" }
@@ -188,7 +194,7 @@
fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
int1 = userId
@@ -212,7 +218,7 @@
fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
int1 = userId
@@ -224,7 +230,7 @@
fun logFingerprintError(msgId: Int, originalErrMsg: String) {
logBuffer.log(
- TAG,
+ FP_LOG_TAG,
DEBUG,
{
str1 = originalErrMsg
@@ -751,4 +757,25 @@
{ "userSwitchComplete: $str1, userId: $int1" }
)
}
+
+ fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) {
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ {
+ int1 = helpMsgId
+ str1 = "$helpString"
+ },
+ { "fingerprint help message: $int1, $str1" }
+ )
+ }
+
+ fun logFingerprintAcquired(acquireInfo: Int) {
+ logBuffer.log(
+ FP_LOG_TAG,
+ DEBUG,
+ { int1 = acquireInfo },
+ { "fingerprint acquire message: $int1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c1f6259..40f229b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -41,6 +41,8 @@
import android.view.Surface
import android.view.View
import android.view.View.AccessibilityDelegate
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
@@ -54,13 +56,13 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.keyguard.KeyguardPINView
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.res.R
import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
@@ -229,6 +231,20 @@
}
}
+ /** Hide the arrow indicator. */
+ fun hideIndicator() {
+ val lottieAnimationView =
+ overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+ lottieAnimationView?.visibility = INVISIBLE
+ }
+
+ /** Show the arrow indicator. */
+ fun showIndicator() {
+ val lottieAnimationView =
+ overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
+ lottieAnimationView?.visibility = VISIBLE
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("requests:")
for (requestSource in requests) {
@@ -247,6 +263,10 @@
pw.println(" displayId=${displayInfo.uniqueId}")
pw.println(" sensorType=${sensorProps?.sensorType}")
pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+ pw.println("lottieAnimationView:")
+ pw.println(
+ " visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}"
+ )
pw.println("overlayOffsets=$overlayOffsets")
pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
@@ -498,5 +518,5 @@
AUTO_SHOW,
/** Pin, pattern or password bouncer */
PRIMARY_BOUNCER,
- ALTERNATE_BOUNCER
+ ALTERNATE_BOUNCER,
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 2f80106..5659623 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -22,6 +22,7 @@
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
+import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -32,6 +33,11 @@
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
+ @IntoMap
+ @ClassKey(SideFpsProgressBarViewBinder::class)
+ fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable
+
+ @Binds
fun keyguardSurfaceBehindRepository(
impl: KeyguardSurfaceBehindRepositoryImpl
): KeyguardSurfaceBehindRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
index ae18681..3c143fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.shared.model
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
+import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
import android.hardware.fingerprint.FingerprintManager
import android.os.SystemClock.elapsedRealtime
@@ -39,7 +41,12 @@
/** Fingerprint acquired message. */
data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) :
- FingerprintAuthenticationStatus()
+ FingerprintAuthenticationStatus() {
+
+ val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START
+
+ val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD
+}
/** Fingerprint authentication failed message. */
object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus()
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
new file mode 100644
index 0000000..1acea5c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+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.util.kotlin.Quint
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewBinder
+@Inject
+constructor(
+ private val viewModel: SideFpsProgressBarViewModel,
+ private val view: SideFpsProgressBar,
+ @Application private val applicationScope: CoroutineScope,
+ private val sfpsController: dagger.Lazy<SideFpsController>,
+) : CoreStartable {
+
+ override fun start() {
+ applicationScope.launch {
+ viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled ->
+ if (enabled) {
+ launch {
+ combine(
+ viewModel.isVisible,
+ viewModel.sensorLocation,
+ viewModel.shouldRotate90Degrees,
+ viewModel.isFingerprintAuthRunning,
+ viewModel.sensorWidth,
+ ::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()
+ }
+ }
+ }
+ launch { viewModel.progress.collectLatest { view.setProgress(it) } }
+ } else {
+ view.hideOverlay()
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..f7ab1ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view
+
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ProgressBar
+import com.android.systemui.biometrics.Utils
+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
+constructor(
+ private val layoutInflater: LayoutInflater,
+ private val windowManager: WindowManager,
+) {
+ private var progressBarWidth = 200
+ fun updateView(
+ visible: Boolean,
+ location: Point,
+ shouldRotate90Degrees: Boolean,
+ progressBarWidth: Int
+ ) {
+ if (visible) {
+ this.progressBarWidth = progressBarWidth
+ createAndShowOverlay(location, shouldRotate90Degrees)
+ } else {
+ hideOverlay()
+ }
+ }
+
+ fun hideOverlay() {
+ overlayView = null
+ }
+
+ private val overlayViewParams =
+ WindowManager.LayoutParams(
+ progressBarHeight,
+ progressBarWidth,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+ PixelFormat.TRANSPARENT
+ )
+ .apply {
+ title = TAG
+ fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+ gravity = Gravity.TOP or Gravity.LEFT
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+ 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
+ ) {
+ if (overlayView == null) {
+ overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
+ }
+ 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)
+ }
+
+ fun setProgress(value: Float) {
+ overlayView
+ ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar)
+ ?.setProgress((value * 100).toInt(), false)
+ }
+}
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
new file mode 100644
index 0000000..2c3b431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.animation.ValueAnimator
+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.isDefaultOrientation
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+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 javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class SideFpsProgressBarViewModel
+@Inject
+constructor(
+ private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val sfpsSensorInteractor: SideFpsSensorInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ private val _progress = MutableStateFlow(0.0f)
+ private val _visible = MutableStateFlow(false)
+ private var _animator: ValueAnimator? = null
+
+ private fun onFingerprintCaptureCompleted() {
+ _visible.value = false
+ _progress.value = 0.0f
+ }
+
+ val isVisible: Flow<Boolean> = _visible.asStateFlow()
+
+ val progress: Flow<Float> = _progress.asStateFlow()
+
+ val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width }
+
+ val sensorLocation: Flow<Point> =
+ sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) }
+
+ val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning
+
+ val shouldRotate90Degrees: Flow<Boolean> =
+ combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair)
+ .map { (rotation, sensorLocation) ->
+ if (rotation.isDefaultOrientation()) {
+ sensorLocation.isSensorVerticalInDefaultOrientation
+ } else {
+ !sensorLocation.isSensorVerticalInDefaultOrientation
+ }
+ }
+
+ val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
+ sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication
+
+ init {
+ applicationScope.launch {
+ combine(
+ sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication,
+ sfpsSensorInteractor.authenticationDuration,
+ ::Pair
+ )
+ .collectLatest { (enabled, authDuration) ->
+ if (!enabled) return@collectLatest
+
+ launch {
+ fpAuthRepository.authenticationStatus.collectLatest { authStatus ->
+ when (authStatus) {
+ is AcquiredFingerprintAuthenticationStatus -> {
+ if (authStatus.fingerprintCaptureStarted) {
+
+ _visible.value = true
+ _animator?.cancel()
+ _animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f)
+ .setDuration(authDuration)
+ .apply {
+ addUpdateListener {
+ _progress.value = it.animatedValue as Float
+ }
+ addListener(
+ doOnEnd {
+ if (_progress.value == 0.0f) {
+ _visible.value = false
+ }
+ }
+ )
+ }
+ _animator?.start()
+ } else if (authStatus.fingerprintCaptureCompleted) {
+ onFingerprintCaptureCompleted()
+ } else {
+ // Abandoned FP Auth attempt
+ _animator?.reverse()
+ }
+ }
+ is ErrorFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is FailFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is SuccessFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ else -> Unit
+ }
+ }
+ }
+ }
+ }
+ }
+}