UDFPS Enrollment Refactor (6/N)

Bug fixes

Bug: 297082837

Change-Id: I86013007f089e9c57e1f7406f327c001bc4099b4
This commit is contained in:
Joshua McCloskey
2024-04-01 23:01:21 +00:00
parent f6849078c5
commit 01b4ef5a92
24 changed files with 659 additions and 363 deletions

View File

@@ -14,87 +14,65 @@
~ See the License for the specific language governing permissions and ~ See the License for the specific language governing permissions and
~ limitations under the License. ~ limitations under the License.
--> -->
<!-- This is used to grab style attributes and apply them
<LinearLayout to this layout -->
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/udfps_layout" android:id="@+id/glif_layout"
style="?attr/fingerprint_layout_theme" style="?attr/fingerprint_layout_theme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> >
<!-- This is used to grab style attributes and apply them
to this layout -->
<com.google.android.setupdesign.GlifLayout
android:id="@+id/dummy_glif_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
/>
<LinearLayout <LinearLayout
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView style="@style/SudContentFrame"
android:id="@+id/sud_layout_icon"
style="@style/SudGlifIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitStart"
android:src="@drawable/ic_lock" />
<TextView
android:id="@+id/title"
style="@style/SudGlifHeaderTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="2"
/>
<TextView
android:id="@+id/description"
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="3"
android:paddingLeft="10dp"
android:paddingRight="10dp"
/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:scaleType="centerInside"
android:visibility="gone"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85"
/>
</LinearLayout>
<FrameLayout
android:id="@+id/layout_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_horizontal|bottom" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical"
> >
<include layout="@layout/fingerprint_v2_udfps_enroll_view" /> <LinearLayout
</FrameLayout> android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center|bottom"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<FrameLayout
android:id="@+id/layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|bottom"
android:clipChildren="false"
android:clipToPadding="false"
>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipChildren="false"
android:clipToPadding="false"
android:scaleType="centerInside"
android:visibility="gone"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85"
/>
<include layout="@layout/fingerprint_v2_udfps_enroll_view" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.setupdesign.GlifLayout>
</LinearLayout>

View File

@@ -19,7 +19,6 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/udfps_layout" android:id="@+id/udfps_layout"
style="?attr/fingerprint_layout_theme"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
@@ -27,65 +26,57 @@
<!-- This is used to grab style attributes and apply them <!-- This is used to grab style attributes and apply them
to this layout --> to this layout -->
<com.google.android.setupdesign.GlifLayout <com.google.android.setupdesign.GlifLayout
android:id="@+id/dummy_glif_layout" android:id="@+id/glif_layout"
style="?attr/fingerprint_layout_theme" style="?attr/fingerprint_layout_theme"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="gone" android:layout_weight="5"
/> >
<ImageView <LinearLayout
android:id="@+id/sud_layout_icon" style="@style/SudContentFrame"
style="@style/SudGlifIcon" android:id="@+id/sud_content_frame"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scaleType="fitStart" >
android:src="@drawable/ic_lock" />
<TextView <com.airbnb.lottie.LottieAnimationView
android:id="@+id/title" android:id="@+id/illustration_lottie"
style="@style/SudGlifHeaderTitle" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="200dp"
android:layout_height="80dp" android:clipChildren="false"
android:ellipsize="end" android:clipToPadding="false"
android:lines="2" android:paddingLeft="10dp"
/> android:paddingRight="10dp"
android:scaleType="centerInside"
<TextView app:lottie_autoPlay="true"
android:id="@+id/description" app:lottie_loop="true"
style="@style/SudDescription.Glif" app:lottie_speed=".85" />
android:layout_width="match_parent" </LinearLayout>
android:layout_height="wrap_content" </com.google.android.setupdesign.GlifLayout>
android:ellipsize="end"
android:lines="3"
android:paddingLeft="10dp"
android:paddingRight="10dp"
/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/illustration_lottie"
android:layout_width="match_parent"
android:layout_height="200dp"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:scaleType="centerInside"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_speed=".85" />
<FrameLayout <FrameLayout
android:id="@+id/layout_container" android:id="@+id/layout_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_weight="4"
android:clipToPadding="false" android:clipToPadding="false"
android:clipChildren="false"
> >
<include layout="@layout/fingerprint_v2_udfps_enroll_view" /> <include layout="@layout/fingerprint_v2_udfps_enroll_view" />
<Button
android:id="@+id/skip"
style="@style/SudGlifButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp"
android:text="@string/security_settings_fingerprint_enroll_enrolling_skip" />
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>

View File

@@ -21,6 +21,8 @@
android:id="@+id/udfps_animation_view" android:id="@+id/udfps_animation_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"> android:orientation="vertical">
<ImageView <ImageView

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -184,6 +184,7 @@ public class UdfpsEnrollHelper extends InstrumentedFragment {
*/ */
public void onAcquired(boolean isAcquiredGood) { public void onAcquired(boolean isAcquiredGood) {
if (mListener != null) { if (mListener != null) {
Log.e("JRM", "OnaCquired " + isAcquiredGood + " lastStepIsGood" + animateIfLastStep());
mListener.onAcquired(isAcquiredGood && animateIfLastStep()); mListener.onAcquired(isAcquiredGood && animateIfLastStep());
} }
} }

View File

@@ -34,7 +34,7 @@ class DebuggingRepositoryImpl : DebuggingRepository {
*/ */
private val isBuildDebuggable = Build.IS_DEBUGGABLE private val isBuildDebuggable = Build.IS_DEBUGGABLE
/** This flag indicates if udfps should use debug repos to supply data to its various views. */ /** This flag indicates if udfps should use debug repos to supply data to its various views. */
private val udfpsEnrollmentDebugEnabled = true private val udfpsEnrollmentDebugEnabled = false
override fun isDebuggingEnabled(): Boolean { override fun isDebuggingEnabled(): Boolean {
return isBuildDebuggable return isBuildDebuggable

View File

@@ -34,10 +34,10 @@ class EnrollStageInteractorImpl() : EnrollStageInteractor {
flowOf( flowOf(
mapOf( mapOf(
0.0f to EnrollStageModel.Center, 0.0f to EnrollStageModel.Center,
0.25f to EnrollStageModel.Guided, 0.065f to EnrollStageModel.Guided,
0.5f to EnrollStageModel.Fingertip, 0.48f to EnrollStageModel.Fingertip,
0.75f to EnrollStageModel.LeftEdge, 0.584f to EnrollStageModel.LeftEdge,
0.875f to EnrollStageModel.RightEdge, 0.792f to EnrollStageModel.RightEdge,
) )
) )
} }

View File

@@ -33,6 +33,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
/** This repository is responsible for collecting all state related to the enroll API. */ /** This repository is responsible for collecting all state related to the enroll API. */
@@ -106,6 +107,30 @@ class FingerprintEnrollInteractorImpl(
streamEnded = true streamEnded = true
enrollRequestOutstanding.update { false } enrollRequestOutstanding.update { false }
} }
override fun onUdfpsPointerDown(sensorId: Int) {
trySend(FingerEnrollState.PointerDown(sensorId)).onFailure { error ->
Log.d(TAG, "onUdfpsPointerDown failed to send, due to $error")
}
}
override fun onUdfpsPointerUp(sensorId: Int) {
trySend(FingerEnrollState.PointerUp(sensorId)).onFailure { error ->
Log.d(TAG, "onUdfpsPointerUp failed to send, due to $error")
}
}
override fun onUdfpsOverlayShown() {
trySend(FingerEnrollState.OverlayShown).onFailure { error ->
Log.d(TAG, "OverlayShown failed to send, due to $error")
}
}
override fun onAcquired(isAcquiredGood: Boolean) {
trySend(FingerEnrollState.Acquired(isAcquiredGood)).onFailure { error ->
Log.d(TAG, "Acquired failed to send, due to $error")
}
}
} }
val cancellationSignal = CancellationSignal() val cancellationSignal = CancellationSignal()

View File

@@ -17,17 +17,12 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.content.Context import android.content.Context
import android.util.Log
import android.view.OrientationEventListener import android.view.OrientationEventListener
import com.android.internal.R import com.android.internal.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform import kotlinx.coroutines.flow.transform
/** Interactor which provides information about orientation */ /** Interactor which provides information about orientation */
@@ -35,7 +30,9 @@ interface OrientationInteractor {
/** A flow that contains the information about the orientation changing */ /** A flow that contains the information about the orientation changing */
val orientation: Flow<Int> val orientation: Flow<Int>
/** /**
* A flow that contains the rotation info * This indicates the surface rotation that hte view is currently in. For instance its possible to
* rotate a view to 90 degrees but for it to still be portrait mode. In this case, this flow
* should emit that we are in rotation 0 (SurfaceView.Rotation_0)
*/ */
val rotation: Flow<Int> val rotation: Flow<Int>
/** /**
@@ -50,8 +47,7 @@ interface OrientationInteractor {
fun getRotationFromDefault(rotation: Int): Int fun getRotationFromDefault(rotation: Int): Int
} }
class OrientationInteractorImpl(private val context: Context, activityScope: CoroutineScope) : class OrientationInteractorImpl(private val context: Context) : OrientationInteractor {
OrientationInteractor {
override val orientation: Flow<Int> = callbackFlow { override val orientation: Flow<Int> = callbackFlow {
val orientationEventListener = val orientationEventListener =
@@ -62,9 +58,12 @@ class OrientationInteractorImpl(private val context: Context, activityScope: Cor
} }
orientationEventListener.enable() orientationEventListener.enable()
awaitClose { orientationEventListener.disable() } awaitClose { orientationEventListener.disable() }
}.shareIn(activityScope, SharingStarted.Eagerly, replay = 1) }
override val rotation: Flow<Int> = orientation.transform { emit(context.display!!.rotation) } override val rotation: Flow<Int> =
orientation.transform {
emit(context.display!!.rotation)
}
override val rotationFromDefault: Flow<Int> = rotation.map { getRotationFromDefault(it) } override val rotationFromDefault: Flow<Int> = rotation.map { getRotationFromDefault(it) }

View File

@@ -17,6 +17,7 @@
package com.android.settings.biometrics.fingerprint2.domain.interactor package com.android.settings.biometrics.fingerprint2.domain.interactor
import android.graphics.PointF import android.graphics.PointF
import android.util.Log
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@@ -69,6 +70,7 @@ class UdfpsEnrollInteractorImpl(
override fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int) { override fun onEnrollmentStep(stepsRemaining: Int, totalStep: Int) {
val index = (totalStep - stepsRemaining) % guidedEnrollmentPoints.size val index = (totalStep - stepsRemaining) % guidedEnrollmentPoints.size
Log.e("JRM", "guided enroll step $index")
_guidedEnrollment.update { guidedEnrollmentPoints[index] } _guidedEnrollment.update { guidedEnrollmentPoints[index] }
} }

View File

@@ -47,10 +47,10 @@ sealed class FingerEnrollState {
data class Acquired(val acquiredGood: Boolean) : FingerEnrollState() data class Acquired(val acquiredGood: Boolean) : FingerEnrollState()
/** Indicates a pointer down event has occurred */ /** Indicates a pointer down event has occurred */
data object PointerDown : FingerEnrollState() data class PointerDown(val fingerId: Int) : FingerEnrollState()
/** Indicates a pointer up event has occurred */ /** Indicates a pointer up event has occurred */
data object PointerUp : FingerEnrollState() data class PointerUp(val fingerId: Int) : FingerEnrollState()
/** Indicates the overlay has shown */ /** Indicates the overlay has shown */
data object OverlayShown : FingerEnrollState() data object OverlayShown : FingerEnrollState()

View File

@@ -72,6 +72,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.fragment.RFPSEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.viewmodel.RFPSViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.fragment.UdfpsEnrollFragment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsLastStepViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.BackgroundViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
@@ -126,6 +127,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
private lateinit var fingerprintEnrollConfirmationViewModel: private lateinit var fingerprintEnrollConfirmationViewModel:
FingerprintEnrollConfirmationViewModel FingerprintEnrollConfirmationViewModel
private lateinit var udfpsLastStepViewModel: UdfpsLastStepViewModel
private lateinit var udfpsViewModel: UdfpsViewModel private lateinit var udfpsViewModel: UdfpsViewModel
private lateinit var enrollStageInteractor: EnrollStageInteractor private lateinit var enrollStageInteractor: EnrollStageInteractor
private val coroutineDispatcher = Dispatchers.Default private val coroutineDispatcher = Dispatchers.Default
@@ -320,7 +322,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
foldStateInteractor = FoldStateInteractorImpl(context) foldStateInteractor = FoldStateInteractorImpl(context)
foldStateInteractor.onConfigurationChange(resources.configuration) foldStateInteractor.onConfigurationChange(resources.configuration)
orientationInteractor = OrientationInteractorImpl(context, lifecycleScope) orientationInteractor = OrientationInteractorImpl(context)
vibrationInteractor = vibrationInteractor =
VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context) VibrationInteractorImpl(context.getSystemService(Vibrator::class.java)!!, context)
@@ -373,11 +375,15 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
fingerprintEnrollEnrollingViewModel, fingerprintEnrollEnrollingViewModel,
navigationViewModel, navigationViewModel,
orientationInteractor, orientationInteractor,
fingerprintManagerInteractor,
), ),
)[RFPSViewModel::class.java] )[RFPSViewModel::class.java]
enrollStageInteractor = EnrollStageInteractorImpl() enrollStageInteractor = EnrollStageInteractorImpl()
udfpsLastStepViewModel =
UdfpsLastStepViewModel(fingerprintEnrollEnrollingViewModel, vibrationInteractor)
udfpsViewModel = udfpsViewModel =
ViewModelProvider( ViewModelProvider(
this, this,
@@ -393,6 +399,9 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
backgroundViewModel, backgroundViewModel,
fingerprintSensorRepo, fingerprintSensorRepo,
udfpsEnrollInteractor, udfpsEnrollInteractor,
fingerprintManagerInteractor,
udfpsLastStepViewModel,
accessibilityInteractor,
), ),
)[UdfpsViewModel::class.java] )[UdfpsViewModel::class.java]
@@ -456,7 +465,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
step.exitTransition.toAnimation(), step.exitTransition.toAnimation(),
) )
.setReorderingAllowed(true) .setReorderingAllowed(true)
.add(R.id.fragment_container_view, theClass::class.java, null) .replace(R.id.fragment_container_view, theClass::class.java, null)
.commit() .commit()
navigationViewModel.update( navigationViewModel.update(
FingerprintAction.TRANSITION_FINISHED, FingerprintAction.TRANSITION_FINISHED,

View File

@@ -20,15 +20,18 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep.Enrollment
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -41,6 +44,7 @@ class RFPSViewModel(
private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel, private val fingerprintEnrollViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel, private val navigationViewModel: FingerprintNavigationViewModel,
orientationInteractor: OrientationInteractor, orientationInteractor: OrientationInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
) : ViewModel() { ) : ViewModel() {
private val _textViewIsVisible = MutableStateFlow(false) private val _textViewIsVisible = MutableStateFlow(false)
@@ -52,7 +56,16 @@ class RFPSViewModel(
/** Indicates if the icon should be animating or not */ /** Indicates if the icon should be animating or not */
val shouldAnimateIcon = _shouldAnimateIcon val shouldAnimateIcon = _shouldAnimateIcon
private var enrollFlow: Flow<FingerEnrollState?> = fingerprintEnrollViewModel.enrollFlow private var enrollFlow: Flow<FingerEnrollState?> =
fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
fingerprintEnrollViewModel.enrollFlow
) { props, enroll ->
if (props.sensorType == FingerprintSensorType.REAR) {
enroll
} else {
null
}
}
/** /**
* Enroll progress message with a replay of size 1 allowing for new subscribers to get the most * Enroll progress message with a replay of size 1 allowing for new subscribers to get the most
@@ -149,6 +162,7 @@ class RFPSViewModel(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel, private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val navigationViewModel: FingerprintNavigationViewModel, private val navigationViewModel: FingerprintNavigationViewModel,
private val orientationInteractor: OrientationInteractor, private val orientationInteractor: OrientationInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -157,6 +171,7 @@ class RFPSViewModel(
fingerprintEnrollEnrollingViewModel, fingerprintEnrollEnrollingViewModel,
navigationViewModel, navigationViewModel,
orientationInteractor, orientationInteractor,
fingerprintManager,
) )
as T as T
} }

View File

@@ -21,8 +21,10 @@ import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import android.view.MotionEvent.ACTION_HOVER_MOVE import android.view.MotionEvent.ACTION_HOVER_MOVE
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.TextView import android.widget.Button
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@@ -40,7 +42,6 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enroll
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.EducationAnimationModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2 import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.widget.UdfpsEnrollViewV2
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import com.google.android.setupdesign.GlifLayout import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -71,10 +72,8 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
val fragment = this val fragment = this
lottie = view.findViewById(R.id.illustration_lottie)!! lottie = view.findViewById(R.id.illustration_lottie)!!
udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!! udfpsEnrollView = view.findViewById(R.id.udfps_animation_view)!!
val titleTextView = view.findViewById<TextView>(R.id.title)!! val glifLayout: GlifLayout = view.findViewById(R.id.glif_layout)!!
val descriptionTextView = view.findViewById<TextView>(R.id.description)!!
val glifLayout = view.findViewById<GlifLayout>(R.id.dummy_glif_layout)!!
val backgroundColor = glifLayout.backgroundBaseColor val backgroundColor = glifLayout.backgroundBaseColor
val window = requireActivity().window val window = requireActivity().window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
@@ -83,6 +82,10 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
window.statusBarColor = color window.statusBarColor = color
view.setBackgroundColor(color) view.setBackgroundColor(color)
view.findViewById<Button>(R.id.skip)?.apply {
setOnClickListener { viewModel.negativeButtonClicked() }
}
udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() } udfpsEnrollView.setFinishAnimationCompleted { viewModel.finishedSuccessfully() }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
@@ -92,32 +95,41 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType) udfpsEnrollView.setSensorRect(sensor.sensorBounds, sensor.sensorType)
} }
} }
viewLifecycleOwner.lifecycleScope.launch {
viewModel.headerText.collect { titleTextView.setText(it.toResource()) }
}
viewLifecycleOwner.lifecycleScope.launch { launch { viewModel.overlayShown.collect { udfpsEnrollView.overlayShown() } }
viewModel.descriptionText.collect { launch { viewModel.headerText.collect { glifLayout.setHeaderText(it.toResource()) } }
if (it != null) { launch {
it.toResource()?.let { text -> descriptionTextView.setText(text) } viewModel.userInteractedWithSensor.collect {
} else { if (!it) {
descriptionTextView.text = "" glifLayout.setHeaderText(R.string.security_settings_fingerprint_enroll_udfps_title)
glifLayout.setDescriptionText(R.string.security_settings_udfps_enroll_start_message)
} }
} }
} }
viewLifecycleOwner.lifecycleScope.launch {
launch {
viewModel.descriptionText.collect {
if (it != null) {
it.toResource()?.let { text -> glifLayout.setDescriptionText(text) }
} else {
glifLayout.descriptionText = ""
}
}
}
launch {
viewModel.shouldShowLottie.collect { viewModel.shouldShowLottie.collect {
lottie.visibility = if (it) View.VISIBLE else View.GONE lottie.visibility = if (it) View.VISIBLE else View.GONE
} }
} }
viewLifecycleOwner.lifecycleScope.launch { launch {
viewModel.lottie.collect { lottieModel -> viewModel.lottie.collect { lottieModel ->
if (lottie.visibility == View.GONE) { if (lottie.visibility == View.GONE) {
return@collect return@collect
} }
val resource = lottieModel.toResource() val resource = lottieModel.toResource()
if (resource != null) { if (resource != null) {
glifLayout.descriptionTextView.visibility = View.GONE
LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp -> LottieCompositionFactory.fromRawRes(requireContext(), resource).addListener { comp ->
comp?.let { composition -> comp?.let { composition ->
lottie.setComposition(composition) lottie.setComposition(composition)
@@ -126,27 +138,24 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
} }
} }
} else { } else {
glifLayout.descriptionTextView.visibility = View.VISIBLE
lottie.visibility = View.INVISIBLE lottie.visibility = View.INVISIBLE
} }
} }
} }
launch {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.accessibilityEnabled.collect { enabled -> viewModel.accessibilityEnabled.collect { enabled ->
udfpsEnrollView.setAccessibilityEnabled(enabled) udfpsEnrollView.setAccessibilityEnabled(enabled)
} }
} }
viewLifecycleOwner.lifecycleScope.launch { launch {
viewModel.enrollState.collect { viewModel.enrollState.collect {
Log.d(TAG, "EnrollEvent $it") Log.d(TAG, "EnrollEvent $it")
if (it is FingerEnrollState.EnrollError) { if (it is FingerEnrollState.EnrollError) {
try { try {
FingerprintErrorDialog.showInstance(it, fragment) FingerprintErrorDialog.showInstance(it, fragment)
viewModel.errorDialogShown(it)
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Exception occurred $exception") Log.e(TAG, "Exception occurred $exception")
} }
@@ -156,19 +165,40 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
} }
} }
viewLifecycleOwner.lifecycleScope.launch { launch { viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) } }
viewModel.progressSaved.collect { udfpsEnrollView.onEnrollProgressSaved(it) }
launch { viewModel.guidedEnrollment.collect { udfpsEnrollView.updateGuidedEnrollment(it) } }
launch {
viewModel.guidedEnrollmentSaved.collect { udfpsEnrollView.onGuidedPointSaved(it) }
} }
viewLifecycleOwner.lifecycleScope.launch { launch { viewModel.shouldDrawIcon.collect { udfpsEnrollView.shouldDrawIcon(it) } }
viewModel.guidedEnrollment.collect {
glifLayout.post { udfpsEnrollView.updateGuidedEnrollment(it) } // Hack to get the final step of enroll progress to animate.
launch {
viewModel.udfpsLastStepViewModel.shouldAnimateCompletion.collect {
Log.d(TAG, "Sending fake last enroll event $it")
udfpsEnrollView.onUdfpsEvent(it)
} }
} }
viewLifecycleOwner.lifecycleScope.launch { }
viewModel.guidedEnrollmentSaved.collect { }
glifLayout.post { udfpsEnrollView.onGuidedPointSaved(it) }
} viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.DESTROYED) { viewModel.stopEnrollment() }
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isLandscape.collect {
if (it) {
changeViewToLandscape()
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.isReverseLandscape.collect {
if (it) {
changeViewToReverseLandscape()
} }
} }
} }
@@ -183,12 +213,70 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
viewModel.readyForEnrollment() viewModel.readyForEnrollment()
} }
private fun changeViewToReverseLandscape() {
Log.d(TAG, "changeViewToReverseLandscape")
val glifContainer = requireView().findViewById<GlifLayout>(R.id.glif_layout)!!
val headerView =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_header_area
)
// The landscape_header_area nad landscape_content_area should have the same parent
val parent = headerView!!.parent as ViewGroup
val sudContentFrame =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_content_area
)!!
val udfpsContainer = requireView().findViewById<FrameLayout>(R.id.layout_container)!!
parent.removeView(headerView)
parent.removeView(sudContentFrame)
parent.addView(sudContentFrame)
parent.addView(headerView)
unclipSubviewsFromParent(udfpsContainer)
udfpsEnrollView.requestLayout()
}
private fun changeViewToLandscape() {
Log.d(TAG, "changeViewToLandscape")
val glifContainer = requireView().findViewById<GlifLayout>(R.id.glif_layout)!!
val headerView =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_header_area
)
// The landscape_header_area nad landscape_content_area should have the same parent
val parent = headerView!!.parent as ViewGroup
val sudContentFrame =
glifContainer.findViewById<View>(
com.google.android.setupdesign.R.id.sud_landscape_content_area
)!!
parent.removeView(headerView)
parent.removeView(sudContentFrame)
parent.addView(headerView)
parent.addView(sudContentFrame)
val udfpsContainer = requireView().findViewById<FrameLayout>(R.id.layout_container)!!
unclipSubviewsFromParent(udfpsContainer)
udfpsEnrollView.requestLayout()
}
private fun unclipSubviewsFromParent(view: View) {
var currParent = view.parent
while (currParent is ViewGroup) {
currParent.clipChildren = false
currParent.clipToPadding = false
currParent = currParent.parent
}
}
private fun HeaderText.toResource(): Int { private fun HeaderText.toResource(): Int {
return when (this.enrollStageModel) { return when (this.enrollStageModel) {
EnrollStageModel.Center, EnrollStageModel.Center,
EnrollStageModel.Guided, EnrollStageModel.Guided -> R.string.security_settings_fingerprint_enroll_repeat_title
EnrollStageModel.Fingertip, EnrollStageModel.Fingertip -> R.string.security_settings_udfps_enroll_fingertip_title
EnrollStageModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title EnrollStageModel.Unknown -> R.string.security_settings_fingerprint_enroll_udfps_title
EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title EnrollStageModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title EnrollStageModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
} }
@@ -218,6 +306,5 @@ class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enro
companion object { companion object {
private const val TAG = "UDFPSEnrollFragment" private const val TAG = "UDFPSEnrollFragment"
private val navStep = FingerprintNavigationStep.Enrollment::class
} }
} }

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2024 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.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model
import android.graphics.PointF
import com.android.systemui.biometrics.shared.model.FingerprintSensor
data class UdfpsSensorLocation(val sensorLocation: FingerprintSensor, val offset: PointF?)

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2024 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.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintVibrationEffects
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollEnrollingViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/**
* This class is responsible for determining if we should animate the last step of an enrollment. It
* seems to be due to poor hardware implementation that the last step of a UDFPS enrollment takes
* much longer then normal (likely due to to saving the whole enrollment/or some kind of
* verification)
*
* Because of this, we will use the information of if a fingerprint was acquired(good) + enrollment
* has reached the last step
*/
class UdfpsLastStepViewModel(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val vibrationInteractor: VibrationInteractor,
) : ViewModel() {
private val lastStep: MutableStateFlow<FingerEnrollState.EnrollProgress?> = MutableStateFlow(null)
private val stepSize: MutableStateFlow<Int?> = MutableStateFlow(null)
init {
viewModelScope.launch {
val steps =
fingerprintEnrollEnrollingViewModel.enrollFlow
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.distinctUntilChanged()
.take(2)
.toList()
stepSize.update { steps[0].remainingSteps - steps[1].remainingSteps }
lastStep.update { FingerEnrollState.EnrollProgress(0, steps[0].totalStepsRequired) }
}
}
private val enrollProgress =
fingerprintEnrollEnrollingViewModel.enrollFlow.filterIsInstance<
FingerEnrollState.EnrollProgress
>()
/** Indicates if we should animate the completion of udfps enrollment. */
val shouldAnimateCompletion =
enrollProgress
.transform { it ->
if (it.remainingSteps == stepSize.value) {
fingerprintEnrollEnrollingViewModel.enrollFlow
.filterIsInstance<FingerEnrollState.Acquired>()
.filter { acquired -> acquired.acquiredGood }
.collect {
vibrationInteractor.vibrate(
FingerprintVibrationEffects.UdfpsSuccess,
"UdfpsLastStepViewModel",
)
emit(lastStep.value)
}
}
}
.filterNotNull()
class UdfpsLastStepViewModelFactory(
private val fingerprintEnrollEnrollingViewModel: FingerprintEnrollEnrollingViewModel,
private val vibrationInteractor: VibrationInteractor,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UdfpsLastStepViewModel(fingerprintEnrollEnrollingViewModel, vibrationInteractor) as T
}
}
}

View File

@@ -22,9 +22,10 @@ import android.view.Surface
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel import com.android.settings.biometrics.fingerprint2.data.model.EnrollStageModel
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository import com.android.settings.biometrics.fingerprint2.data.repository.SimulatedTouchEventsRepository
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
@@ -32,6 +33,7 @@ import com.android.settings.biometrics.fingerprint2.domain.interactor.Fingerprin
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState import com.android.settings.biometrics.fingerprint2.lib.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.DescriptionText
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.model.HeaderText
@@ -41,6 +43,7 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.Fing
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationViewModel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.combineTransform
@@ -51,6 +54,8 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */ /** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
@@ -66,13 +71,25 @@ class UdfpsViewModel(
backgroundViewModel: BackgroundViewModel, backgroundViewModel: BackgroundViewModel,
sensorRepository: FingerprintSensorRepository, sensorRepository: FingerprintSensorRepository,
udfpsEnrollInteractor: UdfpsEnrollInteractor, udfpsEnrollInteractor: UdfpsEnrollInteractor,
fingerprintManager: FingerprintManagerInteractor,
val udfpsLastStepViewModel: UdfpsLastStepViewModel,
accessibilityInteractor: AccessibilityInteractor,
) : ViewModel() { ) : ViewModel() {
private val isSetupWizard = flowOf(false) private val isSetupWizard = flowOf(false)
private var shouldResetErollment = false private var shouldResetErollment = false
private var _enrollState: Flow<FingerEnrollState?> = private var _enrollState: Flow<FingerEnrollState?> =
fingerprintEnrollEnrollingViewModel.enrollFlow fingerprintManager.sensorPropertiesInternal.filterNotNull().combine(
fingerprintEnrollEnrollingViewModel.enrollFlow
) { props, enroll ->
if (props.sensorType.isUdfps()) {
enroll
} else {
null
}
}
/** The current state of the enrollment. */ /** The current state of the enrollment. */
var enrollState: Flow<FingerEnrollState> = var enrollState: Flow<FingerEnrollState> =
combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) { combine(fingerprintEnrollEnrollingViewModel.enrollFlowShouldBeRunning, _enrollState) {
@@ -86,48 +103,59 @@ class UdfpsViewModel(
} }
.filterNotNull() .filterNotNull()
/** Indicates that overlay has been shown */
val overlayShown =
enrollState
.filterIsInstance<FingerEnrollState.OverlayShown>()
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
private var _userInteractedWithSensor = MutableStateFlow(false)
/**
* This indicates whether the user has interacted with the sensor or not. This indicates if we are
* in the initial state of the UI.
*/
val userInteractedWithSensor: Flow<Boolean> =
enrollState.transform {
val interactiveMessage =
when (it) {
is FingerEnrollState.Acquired,
is FingerEnrollState.EnrollError,
is FingerEnrollState.EnrollHelp,
is FingerEnrollState.EnrollProgress,
is FingerEnrollState.PointerDown,
is FingerEnrollState.PointerUp -> true
else -> false
}
val hasPreviouslyInteracted = _userInteractedWithSensor.value or interactiveMessage
_userInteractedWithSensor.update { hasPreviouslyInteracted }
emit(hasPreviouslyInteracted)
}
/** /**
* Forwards the property sensor information. This is typically used to recreate views that must be * Forwards the property sensor information. This is typically used to recreate views that must be
* aligned with the sensor. * aligned with the sensor.
*/ */
val sensorLocation = sensorRepository.fingerprintSensor val sensorLocation = sensorRepository.fingerprintSensor
/** Indicates if accessibility is enabled */ /** Indicates a step of guided enrollment, the ui should animate the icon to the new location. */
val accessibilityEnabled = flowOf(true).shareIn(viewModelScope, SharingStarted.Eagerly, 1)
init {
viewModelScope.launch {
enrollState
.combine(accessibilityEnabled) { event, isEnabled -> Pair(event, isEnabled) }
.collect {
if (
when (it.first) {
is FingerEnrollState.EnrollError -> true
is FingerEnrollState.EnrollHelp -> it.second
is FingerEnrollState.EnrollProgress -> true
else -> false
}
) {
vibrate(it.first)
}
}
}
viewModelScope.launch {
backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
}
}
/**
* This indicates at which point the UI should offset the fingerprint sensor icon for guided
* enrollment.
*/
val guidedEnrollment: Flow<PointF> = val guidedEnrollment: Flow<PointF> =
udfpsEnrollInteractor.guidedEnrollmentOffset.distinctUntilChanged() udfpsEnrollInteractor.guidedEnrollmentOffset
.distinctUntilChanged()
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
/** The saved version of [guidedEnrollment] */ private var _lastOrientation: Int? = null
val guidedEnrollmentSaved: Flow<PointF> =
guidedEnrollment.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) /** In case of rotations we should ensure the UI does not re-animate the last state. */
private val shouldReplayLastEvent =
orientationInteractor.rotation.transform {
if (_lastOrientation != null && _lastOrientation!! != it) {
emit(true)
} else {
emit(false)
}
_lastOrientation = it
}
/** /**
* This is the saved progress, this is for when views are recreated and need saved state for the * This is the saved progress, this is for when views are recreated and need saved state for the
@@ -136,8 +164,32 @@ class UdfpsViewModel(
var progressSaved: Flow<FingerEnrollState.EnrollProgress> = var progressSaved: Flow<FingerEnrollState.EnrollProgress> =
enrollState enrollState
.filterIsInstance<FingerEnrollState.EnrollProgress>() .filterIsInstance<FingerEnrollState.EnrollProgress>()
.filterNotNull() .combineTransform(shouldReplayLastEvent) { enroll, shouldReplay ->
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) if (shouldReplay) {
emit(enroll)
}
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
/** Indicates if accessibility is enabled */
val accessibilityEnabled =
accessibilityInteractor.isAccessibilityEnabled.shareIn(
this.viewModelScope,
SharingStarted.Eagerly,
replay = 1,
)
/** Indicates if we are in reverse landscape orientation. */
val isReverseLandscape =
orientationInteractor.rotation
.transform { emit(it == Surface.ROTATION_270) }
.distinctUntilChanged()
/** Indicates if we are in the landscape orientation */
val isLandscape =
orientationInteractor.rotation
.transform { emit(it == Surface.ROTATION_90) }
.distinctUntilChanged()
/** This sends touch exploration events only used for debugging purposes. */ /** This sends touch exploration events only used for debugging purposes. */
val touchExplorationDebug: Flow<Point> = val touchExplorationDebug: Flow<Point> =
@@ -170,6 +222,18 @@ class UdfpsViewModel(
.filterNotNull() .filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
/** The saved version of [guidedEnrollment] */
val guidedEnrollmentSaved: Flow<PointF> =
combineTransform(guidedEnrollment, shouldReplayLastEvent, enrollStage) {
point,
shouldReplay,
stage ->
if (shouldReplay && stage is EnrollStageModel.Guided) {
emit(point)
}
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 1)
init { init {
viewModelScope.launch { viewModelScope.launch {
enrollState enrollState
@@ -200,7 +264,7 @@ class UdfpsViewModel(
} }
viewModelScope.launch { viewModelScope.launch {
backgroundViewModel.background.filter { true }.collect { didGoToBackground() } backgroundViewModel.background.filter { it }.collect { didGoToBackground() }
} }
} }
@@ -210,20 +274,12 @@ class UdfpsViewModel(
displayDensityInteractor.displayDensity, displayDensityInteractor.displayDensity,
displayDensityInteractor.defaultDisplayDensity, displayDensityInteractor.defaultDisplayDensity,
displayDensityInteractor.fontScale, displayDensityInteractor.fontScale,
orientationInteractor.rotation, ) { currDisplayDensity, defaultDisplayDensity, fontScale ->
) { currDisplayDensity, defaultDisplayDensity, fontScale, rotation -> if (fontScale > 1.0f) {
val canShowLottieForRotation = false
when (rotation) { } else {
Surface.ROTATION_0 -> true defaultDisplayDensity == currDisplayDensity
else -> false }
}
canShowLottieForRotation &&
if (fontScale > 1.0f) {
false
} else {
defaultDisplayDensity == currDisplayDensity
}
} }
.shareIn(viewModelScope, SharingStarted.Eagerly, 1) .shareIn(viewModelScope, SharingStarted.Eagerly, 1)
@@ -234,6 +290,18 @@ class UdfpsViewModel(
} }
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1) .shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
/** Indicates if we should or shold not draw the fingerprint icon */
val shouldDrawIcon: Flow<Boolean> =
enrollState.transform { state ->
when (state) {
is FingerEnrollState.EnrollProgress,
is FingerEnrollState.EnrollError,
is FingerEnrollState.PointerUp -> emit(true)
is FingerEnrollState.PointerDown -> emit(false)
else -> {}
}
}
private val shouldClearDescriptionText = enrollStage.map { it is EnrollStageModel.Unknown } private val shouldClearDescriptionText = enrollStage.map { it is EnrollStageModel.Unknown }
/** The description text for UDFPS enrollment */ /** The description text for UDFPS enrollment */
@@ -267,12 +335,12 @@ class UdfpsViewModel(
/** Indicates the negative button has been clicked */ /** Indicates the negative button has been clicked */
fun negativeButtonClicked() { fun negativeButtonClicked() {
doReset()
navigationViewModel.update( navigationViewModel.update(
FingerprintAction.NEGATIVE_BUTTON_PRESSED, FingerprintAction.NEGATIVE_BUTTON_PRESSED,
navStep, navStep,
"$TAG#negativeButtonClicked", "$TAG#negativeButtonClicked",
) )
doReset()
} }
/** Indicates that an enrollment was completed */ /** Indicates that an enrollment was completed */
@@ -282,7 +350,7 @@ class UdfpsViewModel(
} }
/** Indicates that the application went to the background. */ /** Indicates that the application went to the background. */
private fun didGoToBackground() { fun didGoToBackground() {
navigationViewModel.update( navigationViewModel.update(
FingerprintAction.DID_GO_TO_BACKGROUND, FingerprintAction.DID_GO_TO_BACKGROUND,
navStep, navStep,
@@ -293,11 +361,7 @@ class UdfpsViewModel(
private fun doReset() { private fun doReset() {
_enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow _enrollState = fingerprintEnrollEnrollingViewModel.enrollFlow
progressSaved = _userInteractedWithSensor.update { false }
enrollState
.filterIsInstance<FingerEnrollState.EnrollProgress>()
.filterNotNull()
.shareIn(this.viewModelScope, SharingStarted.Eagerly, replay = 1)
} }
/** The lottie that should be shown for UDFPS Enrollment */ /** The lottie that should be shown for UDFPS Enrollment */
@@ -320,6 +384,15 @@ class UdfpsViewModel(
vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment") vibrationInteractor.vibrate(vibrationEvent, "UdfpsEnrollFragment")
} }
/** Indicates an error sent by the HAL has been acknowledged by the user */
fun errorDialogShown(it: FingerEnrollState.EnrollError) {
navigationViewModel.update(
FingerprintAction.USER_CLICKED_FINISH,
navStep,
"${TAG}#userClickedStopEnrollingDialog",
)
}
class UdfpsEnrollmentFactory( class UdfpsEnrollmentFactory(
private val vibrationInteractor: VibrationInteractor, private val vibrationInteractor: VibrationInteractor,
private val displayDensityInteractor: DisplayDensityInteractor, private val displayDensityInteractor: DisplayDensityInteractor,
@@ -332,6 +405,9 @@ class UdfpsViewModel(
private val backgroundViewModel: BackgroundViewModel, private val backgroundViewModel: BackgroundViewModel,
private val sensorRepository: FingerprintSensorRepository, private val sensorRepository: FingerprintSensorRepository,
private val udfpsEnrollInteractor: UdfpsEnrollInteractor, private val udfpsEnrollInteractor: UdfpsEnrollInteractor,
private val fingerprintManager: FingerprintManagerInteractor,
private val udfpsLastStepViewModel: UdfpsLastStepViewModel,
private val accessibilityInteractor: AccessibilityInteractor,
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -348,6 +424,9 @@ class UdfpsViewModel(
backgroundViewModel, backgroundViewModel,
sensorRepository, sensorRepository,
udfpsEnrollInteractor, udfpsEnrollInteractor,
fingerprintManager,
udfpsLastStepViewModel,
accessibilityInteractor,
) )
as T as T
} }

View File

@@ -36,8 +36,8 @@ import android.util.PathParser
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.animation.addListener import androidx.core.animation.addListener
import androidx.core.graphics.toRect import androidx.core.graphics.toRect
import androidx.core.graphics.toRectF
import com.android.settings.R import com.android.settings.R
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlin.math.sin import kotlin.math.sin
/** /**
@@ -45,7 +45,6 @@ import kotlin.math.sin
* various stages of enrollment * various stages of enrollment
*/ */
class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() { class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeSet?) : Drawable() {
private var targetAnimationDuration: Long = TARGET_ANIM_DURATION_LONG
private var targetAnimatorSet: AnimatorSet? = null private var targetAnimatorSet: AnimatorSet? = null
private val movingTargetFpIcon: Drawable private val movingTargetFpIcon: Drawable
private val fingerprintDrawable: ShapeDrawable private val fingerprintDrawable: ShapeDrawable
@@ -55,7 +54,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
@ColorInt private var movingTargetFill = 0 @ColorInt private var movingTargetFill = 0
private var currentScale = 1.0f private var currentScale = 1.0f
private var alpha = 0 private var alpha = 0
private var guidedEnrollmentOffset: PointF? = null private var stopDrawing = false
/** /**
* This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt] * This is the physical location of the sensor. This rect will be updated by [drawSensorRectAt]
@@ -63,15 +62,12 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
private val sensorRectBounds: Rect = Rect() private val sensorRectBounds: Rect = Rect()
/** /**
* The following values are used to describe where the icon should be drawn. [currX] and [currY] * The following values are used to describe where the icon should be drawn. [sensorLeftOffset]
* are changed based on the current guided enrollment step which is given by the * and [sensorTopOffset] are changed based on the current guided enrollment step which is given by
* [UdfpsEnrollHelperV2] * the [UdfpsEnrollHelperV2]
*/ */
private var currX = 0f private var sensorLeftOffset = 0f
private var currY = 0f private var sensorTopOffset = 0f
private var sensorWidth = 0f
private var sensorHeight = 0f
init { init {
fingerprintDrawable = createUdfpsIcon(context) fingerprintDrawable = createUdfpsIcon(context)
@@ -131,29 +127,35 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
* The [sensorRect] coordinates for the sensor area. The [sensorRect] should be the coordinates * The [sensorRect] coordinates for the sensor area. The [sensorRect] should be the coordinates
* with respect to its root frameview * with respect to its root frameview
*/ */
fun drawSensorRectAt(sensorRect: Rect) { fun drawSensorRectAt(overlayParams: UdfpsOverlayParams) {
Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect($sensorRect)") Log.e(TAG, "UdfpsEnrollIcon#drawSensorRect(${overlayParams.sensorBounds})")
val sensorRect = overlayParams.sensorBounds
sensorRectBounds.set(sensorRect) sensorRectBounds.set(sensorRect)
fingerprintDrawable.bounds = sensorRect fingerprintDrawable.bounds = sensorRect
movingTargetFpIcon.bounds = sensorRect movingTargetFpIcon.bounds = sensorRect
currX = sensorRect.left.toFloat()
currY = sensorRect.top.toFloat() // End existing animation if we get an update of the sensor rect.
sensorWidth = (sensorRect.right - sensorRect.left).toFloat() targetAnimatorSet?.end()
sensorHeight = (sensorRect.bottom - sensorRect.top).toFloat()
invalidateSelf() invalidateSelf()
} }
/** Stop drawing the fingerprint icon. */ /** Stop drawing the fingerprint icon. */
fun stopDrawing() { fun stopDrawing() {
alpha = 0 stopDrawing = true
invalidateSelf()
} }
/** Resume drawing the fingerprint icon */ /** Resume drawing the fingerprint icon */
fun startDrawing() { fun startDrawing() {
alpha = 255 stopDrawing = false
invalidateSelf()
} }
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
if (stopDrawing) {
return
}
movingTargetFpIcon.alpha = alpha movingTargetFpIcon.alpha = alpha
fingerprintDrawable.setAlpha(alpha) fingerprintDrawable.setAlpha(alpha)
sensorOutlinePaint.setAlpha(alpha) sensorOutlinePaint.setAlpha(alpha)
@@ -165,23 +167,28 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
fingerprintDrawable.draw(canvas) fingerprintDrawable.draw(canvas)
} }
private fun getCurrLocation(): RectF = private fun getCurrLocation(): RectF {
RectF(currX, currY, currX + sensorWidth, currY + sensorHeight) val x = sensorRectBounds.left + sensorLeftOffset
val y = sensorRectBounds.top + sensorTopOffset
return RectF(x, y, x + sensorRectBounds.width(), y + sensorRectBounds.height())
}
private fun animateMovement(currentBounds: Rect, offsetRect: RectF, scaleMovement: Boolean) { private fun animateMovement(leftOffset: Float, topOffset: Float, scaleMovement: Boolean) {
if (currentBounds.equals(offsetRect)) { if (leftOffset == sensorLeftOffset && topOffset == sensorTopOffset) {
return return
} }
val xAnimator = ValueAnimator.ofFloat(currentBounds.left.toFloat(), offsetRect.left) val currLocation = getCurrLocation()
val xAnimator = ValueAnimator.ofFloat(currLocation.left - sensorRectBounds.left, leftOffset)
xAnimator.addUpdateListener { xAnimator.addUpdateListener {
currX = it.animatedValue as Float sensorLeftOffset = it.animatedValue as Float
invalidateSelf() invalidateSelf()
} }
val yAnimator = ValueAnimator.ofFloat(currentBounds.top.toFloat(), offsetRect.top) val yAnimator = ValueAnimator.ofFloat(currLocation.top - sensorRectBounds.top, topOffset)
yAnimator.addUpdateListener { yAnimator.addUpdateListener {
currY = it.animatedValue as Float sensorTopOffset = it.animatedValue as Float
invalidateSelf() invalidateSelf()
} }
val animators = mutableListOf(xAnimator, yAnimator) val animators = mutableListOf(xAnimator, yAnimator)
@@ -199,6 +206,7 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
animators.add(scaleAnimator) animators.add(scaleAnimator)
} }
targetAnimatorSet?.cancel()
targetAnimatorSet = AnimatorSet() targetAnimatorSet = AnimatorSet()
targetAnimatorSet?.let { targetAnimatorSet?.let {
@@ -209,51 +217,14 @@ class UdfpsEnrollIconV2 internal constructor(context: Context, attrs: AttributeS
} }
} }
/**
* This sets animation time to 0. This typically happens after an activity recreation, we don't
* want to re-animate the progress/success animation with the default timer
*/
private fun setAnimationTimeToZero() {
targetAnimationDuration = 0
}
/** This sets animation timers back to normal, this happens after we have */
private fun restoreAnimationTime() {
targetAnimationDuration = TARGET_ANIM_DURATION_LONG
}
/** /**
* Indicates a change to guided enrollment has occurred. Also indicates if we are recreating the * Indicates a change to guided enrollment has occurred. Also indicates if we are recreating the
* view, in which case their is no need to animate the icon to whatever position it was in. * view, in which case their is no need to animate the icon to whatever position it was in.
*/ */
fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) { fun updateGuidedEnrollment(point: PointF, isRecreating: Boolean) {
guidedEnrollmentOffset = point val pointIsZero = point.x == 0f && point.y == 0f
if (isRecreating) { val shouldAnimateMovement = pointIsZero || !isRecreating
setAnimationTimeToZero() animateMovement(point?.x ?: 0f, point?.y ?: 0f, shouldAnimateMovement)
} else {
restoreAnimationTime()
}
val currentBounds = getCurrLocation().toRect()
val offset = guidedEnrollmentOffset
if (offset?.x != 0f && offset?.y != 0f) {
val targetRect = Rect(sensorRectBounds).toRectF()
// This is the desired location of the sensor rect, the [EnrollHelper]
// offsets the initial sensor rect by a bit to get the user to move their finger a bit more.
targetRect.offset(offset!!.x, offset!!.y)
val shouldAnimateMovement = !currentBounds.equals(targetRect)
if (shouldAnimateMovement) {
targetAnimatorSet?.cancel()
animateMovement(currentBounds, targetRect, true)
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
} else {
// If we are not offsetting the sensor, move it back to its original place
animateMovement(currentBounds, sensorRectBounds.toRectF(), false)
}
invalidateSelf()
} }
companion object { companion object {

View File

@@ -27,16 +27,15 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.animation.addListener
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.graphics.toRectF import androidx.core.graphics.toRectF
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
import com.android.settings.R import com.android.settings.R
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.max import kotlin.math.max
import kotlin.math.sin import kotlin.math.sin
@@ -145,7 +144,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
/** Indicates enrollment progress has occurred. */ /** Indicates enrollment progress has occurred. */
fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) { fun onEnrollmentProgress(remaining: Int, totalSteps: Int, isRecreating: Boolean = false) {
afterFirstTouch = true afterFirstTouch = true
updateProgress(remaining, totalSteps, isRecreating) updateProgress(remaining, totalSteps, isRecreating)
} }
@@ -216,8 +214,8 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
* Draws the progress with locations [sensorLocationX] [sensorLocationY], note these must be with * Draws the progress with locations [sensorLocationX] [sensorLocationY], note these must be with
* respect to the parent framelayout. * respect to the parent framelayout.
*/ */
fun drawProgressAt(sensorRect: Rect) { fun drawProgressAt(overlayParams: UdfpsOverlayParams) {
this.sensorRect.set(sensorRect) this.sensorRect.set(overlayParams.sensorBounds)
invalidateSelf() invalidateSelf()
} }
@@ -249,8 +247,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
restoreAnimationTime() restoreAnimationTime()
} }
this.remainingSteps = remainingSteps
this.totalSteps = totalSteps
this.remainingSteps = remainingSteps this.remainingSteps = remainingSteps
this.totalSteps = totalSteps this.totalSteps = totalSteps
val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps)) val targetProgress = (totalSteps - remainingSteps).toFloat().div(max(1, totalSteps))
@@ -290,12 +286,8 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
checkMarkDrawable.bounds = newBounds checkMarkDrawable.bounds = newBounds
checkMarkDrawable.setVisible(true, false) checkMarkDrawable.setVisible(true, false)
} }
doOnEnd { doOnEnd { onFinishedCompletionAnimation?.let { it() } }
onFinishedCompletionAnimation?.let{
it()
}
}
start() start()
} }
} }
@@ -356,6 +348,7 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
private fun flashHelpFillColor() { private fun flashHelpFillColor() {
if (fillColorAnimator != null && fillColorAnimator!!.isRunning) { if (fillColorAnimator != null && fillColorAnimator!!.isRunning) {
fillColorAnimator!!.end() fillColorAnimator!!.end()
fillColorAnimator = null
} }
@ColorInt val targetColor = helpColor @ColorInt val targetColor = helpColor
fillColorAnimator = fillColorAnimator =
@@ -375,7 +368,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
* want to re-animate the progress/success animation with the default timer * want to re-animate the progress/success animation with the default timer
*/ */
private fun setAnimationTimeToZero() { private fun setAnimationTimeToZero() {
fillColorAnimationDuration = 0
animateArcDuration = 0 animateArcDuration = 0
checkmarkAnimationDelayDuration = 0 checkmarkAnimationDelayDuration = 0
checkmarkAnimationDuration = 0 checkmarkAnimationDuration = 0
@@ -383,7 +375,6 @@ class UdfpsEnrollProgressBarDrawableV2(private val context: Context, attrs: Attr
/** This sets animation timers back to normal, this happens after we have */ /** This sets animation timers back to normal, this happens after we have */
private fun restoreAnimationTime() { private fun restoreAnimationTime() {
fillColorAnimationDuration = FILL_COLOR_ANIMATION_DURATION_MS
animateArcDuration = PROGRESS_ANIMATION_DURATION_MS animateArcDuration = PROGRESS_ANIMATION_DURATION_MS
checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS checkmarkAnimationDelayDuration = CHECKMARK_ANIMATION_DELAY_MS
checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS checkmarkAnimationDuration = CHECKMARK_ANIMATION_DURATION_MS

View File

@@ -70,27 +70,11 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) { fun setSensorRect(rect: Rect, sensorType: FingerprintSensorType) {
this.sensorRect = rect this.sensorRect = rect
this.fingerprintSensorType = sensorType this.fingerprintSensorType = sensorType
findViewById<ImageView?>(R.id.udfps_enroll_animation_fp_progress_view)?.also {
it.setImageDrawable(fingerprintProgressDrawable)
}
findViewById<ImageView>(R.id.udfps_enroll_animation_fp_view)?.also {
it.setImageDrawable(fingerprintIcon)
}
val rotation = display.rotation
var displayInfo = DisplayInfo() var displayInfo = DisplayInfo()
context.display.getDisplayInfo(displayInfo) context.display.getDisplayInfo(displayInfo)
val rotation = displayInfo.rotation
val scaleFactor = udfpsUtils.getScaleFactor(displayInfo) val scaleFactor = udfpsUtils.getScaleFactor(displayInfo)
val overlayParams =
UdfpsOverlayParams(
sensorRect,
fingerprintProgressDrawable.bounds,
displayInfo.naturalWidth,
displayInfo.naturalHeight,
scaleFactor,
rotation,
sensorType.toInt(),
)
val parentView = parent as ViewGroup val parentView = parent as ViewGroup
val coords = parentView.getLocationOnScreen() val coords = parentView.getLocationOnScreen()
val parentLeft = coords[0] val parentLeft = coords[0]
@@ -99,22 +83,44 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
// If the view has been rotated, we need to translate the sensor coordinates // If the view has been rotated, we need to translate the sensor coordinates
// to the new rotated view. // to the new rotated view.
when (rotation) { when (rotation) {
Surface.ROTATION_90, Surface.ROTATION_90 -> {
Surface.ROTATION_270 -> {
sensorRectOffset.set( sensorRectOffset.set(
sensorRectOffset.top, sensorRectOffset.top,
sensorRectOffset.left, sensorRectOffset.left,
sensorRectOffset.bottom, sensorRectOffset.bottom,
sensorRectOffset.right, sensorRectOffset.right,
) )
sensorRectOffset.offset(-parentLeft, -parentTop)
} }
else -> {} // When the view is rotated 270 degrees, 0,0 is the top corner left
} Surface.ROTATION_270 -> {
// Translate the sensor position into UdfpsEnrollView's view space. sensorRectOffset.set(
sensorRectOffset.offset(-parentLeft, -parentTop) (displayInfo.naturalHeight - sensorRectOffset.bottom) - parentLeft,
sensorRectOffset.left - parentTop,
(displayInfo.naturalHeight - sensorRectOffset.top) - parentLeft,
sensorRectOffset.right - parentTop,
)
}
else -> {
fingerprintIcon.drawSensorRectAt(sensorRectOffset) sensorRectOffset.offset(-parentLeft, -parentTop)
fingerprintProgressDrawable.drawProgressAt(sensorRectOffset) }
}
// Translate the sensor position into UdfpsEnrollView's view space.
val overlayParams =
UdfpsOverlayParams(
sensorRectOffset,
fingerprintProgressDrawable.bounds,
displayInfo.naturalWidth,
displayInfo.naturalHeight,
scaleFactor,
rotation,
sensorType.toInt(),
)
fingerprintIcon.drawSensorRectAt(overlayParams)
fingerprintProgressDrawable.drawProgressAt(overlayParams)
touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils) touchExplorationAnnouncer = TouchExplorationAnnouncer(context, this, overlayParams, udfpsUtils)
} }
@@ -126,11 +132,8 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired) onEnrollmentProgress(event.remainingSteps, event.totalStepsRequired)
is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood) is FingerEnrollState.Acquired -> onAcquired(event.acquiredGood)
is FingerEnrollState.EnrollHelp -> onEnrollmentHelp() is FingerEnrollState.EnrollHelp -> onEnrollmentHelp()
is FingerEnrollState.PointerDown -> onPointerDown() // Else ignore
is FingerEnrollState.PointerUp -> onPointerUp() else -> {}
is FingerEnrollState.OverlayShown -> overlayShown()
is FingerEnrollState.EnrollError ->
throw IllegalArgumentException("$TAG should not handle udfps error")
} }
} }
@@ -145,7 +148,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
} }
} }
private fun udfpsError(errMsgId: Int, errString: String) {}
/** /**
* Sends a touch exploration event to the [onHoverListener] this should only be used for * Sends a touch exploration event to the [onHoverListener] this should only be used for
* debugging. * debugging.
@@ -170,8 +172,15 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
onHoverListener = listener onHoverListener = listener
} }
private fun overlayShown() { /** Indicates the overlay has been shown */
Log.e(TAG, "Implement overlayShown") fun overlayShown() {
Log.d(TAG, "Showing udfps overlay")
findViewById<ImageView?>(R.id.udfps_enroll_animation_fp_progress_view)?.also {
it.setImageDrawable(fingerprintProgressDrawable)
}
findViewById<ImageView>(R.id.udfps_enroll_animation_fp_view)?.also {
it.setImageDrawable(fingerprintIcon)
}
} }
/** Receive enroll progress event */ /** Receive enroll progress event */
@@ -190,16 +199,6 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired() if (animateIfLastStepGood) fingerprintProgressDrawable.onLastStepAcquired()
} }
/** Receive onPointerDown event */
private fun onPointerDown() {
fingerprintIcon.stopDrawing()
}
/** Receive onPointerUp event */
private fun onPointerUp() {
fingerprintIcon.startDrawing()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom) super.onLayout(changed, left, top, right, bottom)
// Because the layout has changed, we need to recompute all locations. // Because the layout has changed, we need to recompute all locations.
@@ -261,6 +260,17 @@ class UdfpsEnrollViewV2(context: Context, attrs: AttributeSet?) : FrameLayout(co
fingerprintIcon.updateGuidedEnrollment(point, false) fingerprintIcon.updateGuidedEnrollment(point, false)
} }
/** Indicates if the enroll icon should be drawn. */
fun shouldDrawIcon(it: Boolean) {
post {
if (it) {
fingerprintIcon.startDrawing()
} else {
fingerprintIcon.stopDrawing()
}
}
}
companion object { companion object {
private const val TAG = "UdfpsEnrollView" private const val TAG = "UdfpsEnrollView"
} }

View File

@@ -16,6 +16,7 @@
package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel package com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.android.systemui.biometrics.shared.model.FingerprintSensor import com.android.systemui.biometrics.shared.model.FingerprintSensor
@@ -68,7 +69,8 @@ class FingerprintEnrollEnrollingViewModel(
val enrollFlow = val enrollFlow =
enrollFlowShouldBeRunning.transformLatest { enrollFlowShouldBeRunning.transformLatest {
if (it) { if (it) {
fingerprintEnrollViewModel.enrollFlow.collect { event -> emit(event) } fingerprintEnrollViewModel.enrollFlow.collect { event ->
emit(event) }
} }
} }
@@ -82,4 +84,8 @@ class FingerprintEnrollEnrollingViewModel(
as T as T
} }
} }
companion object {
private val TAG = "FingerprintEnrollEnrollingViewModel"
}
} }

View File

@@ -118,7 +118,12 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
var rfpsIconTouchViewModel = RFPSIconTouchViewModel() var rfpsIconTouchViewModel = RFPSIconTouchViewModel()
var rfpsViewModel = var rfpsViewModel =
RFPSViewModel(fingerprintEnrollEnrollingViewModel, navigationViewModel, orientationInteractor) RFPSViewModel(
fingerprintEnrollEnrollingViewModel,
navigationViewModel,
orientationInteractor,
interactor,
)
val fingerprintEnrollConfirmationViewModel = val fingerprintEnrollConfirmationViewModel =
FingerprintEnrollConfirmationViewModel(navigationViewModel, interactor) FingerprintEnrollConfirmationViewModel(navigationViewModel, interactor)
@@ -151,7 +156,8 @@ class Injector(step: FingerprintNavigationStep.UiStep) {
BackgroundViewModel::class.java -> backgroundViewModel BackgroundViewModel::class.java -> backgroundViewModel
RFPSIconTouchViewModel::class.java -> rfpsIconTouchViewModel RFPSIconTouchViewModel::class.java -> rfpsIconTouchViewModel
FingerprintEnrollEnrollingViewModel::class.java -> fingerprintEnrollEnrollingViewModel FingerprintEnrollEnrollingViewModel::class.java -> fingerprintEnrollEnrollingViewModel
FingerprintEnrollConfirmationViewModel::class.java -> fingerprintEnrollConfirmationViewModel FingerprintEnrollConfirmationViewModel::class.java ->
fingerprintEnrollConfirmationViewModel
else -> null else -> null
} }
as T as T