Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 199d75b3 authored by Joshua McCloskey's avatar Joshua McCloskey Committed by Joshua Mccloskey
Browse files

UDFPS Enrollment Refactor (1/N)

This CL creates a few necessary components that are needed to create the
UI

Test: adb shell device_config put biometrics_framework com.android.settings.flags.fingerprint_v2_enrollment true
Bug: 297082837

Change-Id: I17c4f65fdeac4ebf3c19ba69f5928787b5ace52e
parent 0fe0f4d9
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
<?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.
  -->

<com.google.android.setupdesign.GlifLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/setup_wizard_layout"
    style="?attr/fingerprint_layout_theme"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        style="@style/SudContentFrame"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="center|bottom"
            android:orientation="vertical">

            <FrameLayout
                android:id="@+id/layout_container"
                android:layout_width="200dp"
                android:layout_height="200dp"
                android:layout_gravity="center_horizontal|bottom"
                android:clipChildren="false"
                android:clipToPadding="false"
                tools:ignore="Suspicious0dp">

                <!-- Animation res MUST be set in code -->
                <com.airbnb.lottie.LottieAnimationView
                    android:id="@+id/illustration_lottie"
                    android:layout_width="200dp"
                    android:layout_height="200dp"
                    android:clipChildren="false"
                    android:clipToPadding="false"
                    android:scaleType="centerInside"
                    app:lottie_autoPlay="true"
                    app:lottie_loop="true"
                    app:lottie_speed=".85" />

            </FrameLayout>
        </LinearLayout>
    </LinearLayout>


</com.google.android.setupdesign.GlifLayout>
+11 −0
Original line number Diff line number Diff line
@@ -52,6 +52,8 @@ import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.Finge
import com.android.settings.biometrics.fingerprint2.ui.enrollment.fragment.FingerprintEnrollIntroV2Fragment
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.udfps.ui.fragment.UdfpsEnrollFragment
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.FingerprintAction
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintEnrollConfirmationViewModel
@@ -100,6 +102,7 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
  private lateinit var fingerprintFlowViewModel: FingerprintFlowViewModel
  private lateinit var fingerprintEnrollConfirmationViewModel:
    FingerprintEnrollConfirmationViewModel
  private lateinit var udfpsViewModel: UdfpsViewModel
  private val coroutineDispatcher = Dispatchers.Default

  /** Result listener for ChooseLock activity flow. */
@@ -306,6 +309,12 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
      ),
    )[RFPSViewModel::class.java]

    udfpsViewModel =
      ViewModelProvider(
        this,
        UdfpsViewModel.UdfpsEnrollmentFactory(),
      )[UdfpsViewModel::class.java]

    fingerprintEnrollConfirmationViewModel =
      ViewModelProvider(
        this,
@@ -344,6 +353,8 @@ class FingerprintEnrollmentV2Activity : FragmentActivity() {
              is Enrollment -> {
                when (step.sensor.sensorType) {
                  FingerprintSensorType.REAR -> RFPSEnrollFragment()
                  FingerprintSensorType.UDFPS_OPTICAL,
                  FingerprintSensorType.UDFPS_ULTRASONIC -> UdfpsEnrollFragment()
                  else -> FingerprintEnrollEnrollingV2Fragment()
                }
              }
+127 −0
Original line number Diff line number Diff line
/*
 * 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.fragment

import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.StageViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.udfps.ui.viewmodel.UdfpsViewModel
import com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import com.google.android.setupdesign.GlifLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class UdfpsEnrollFragment() : Fragment(R.layout.fingerprint_v2_udfps_enroll_enrolling) {

  /** Used for testing purposes */
  private var factory: ViewModelProvider.Factory? = null
  private val viewModel: UdfpsViewModel by lazy { viewModelProvider[UdfpsViewModel::class.java] }

  @VisibleForTesting
  constructor(theFactory: ViewModelProvider.Factory) : this() {
    factory = theFactory
  }

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val layout = view as GlifLayout
    val illustrationLottie: LottieAnimationView = layout.findViewById(R.id.illustration_lottie)!!

    viewLifecycleOwner.lifecycleScope.launch {
      repeatOnLifecycle(Lifecycle.State.RESUMED) {
        viewLifecycleOwner.lifecycleScope.launch {
          viewModel.stageFlow.collect {
            layout.setHeaderText(getHeaderText(it))
            getDescriptionText(it)?.let { descriptionText ->
              layout.setDescriptionText(descriptionText)
            }
            getLottie(it)?.let { lottie ->
              layout.descriptionText = ""
              LottieCompositionFactory.fromRawRes(requireContext().applicationContext, lottie)
                .addListener { comp ->
                  comp?.let { composition ->
                    viewLifecycleOwner.lifecycleScope.launch {
                        illustrationLottie.setComposition(composition)
                        illustrationLottie.visibility = View.VISIBLE
                        illustrationLottie.playAnimation()
                      }
                  }
                }
            }
          }
        }
      }
    }
  }

  private fun getHeaderText(stageViewModel: StageViewModel): Int {
    return when (stageViewModel) {
      StageViewModel.Center,
      StageViewModel.Guided,
      StageViewModel.Fingertip,
      StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_fingertip_title
      StageViewModel.LeftEdge -> R.string.security_settings_udfps_enroll_left_edge_title
      StageViewModel.RightEdge -> R.string.security_settings_udfps_enroll_right_edge_title
    }
  }

  private fun getDescriptionText(stageViewModel: StageViewModel): Int? {
    return when (stageViewModel) {
      StageViewModel.Center,
      StageViewModel.Guided,
      StageViewModel.Fingertip,
      StageViewModel.LeftEdge,
      StageViewModel.RightEdge -> null
      StageViewModel.Unknown -> R.string.security_settings_udfps_enroll_start_message
    }
  }

  private fun getLottie(stageViewModel: StageViewModel): Int? {
    return when (stageViewModel) {
      StageViewModel.Center,
      StageViewModel.Guided -> R.raw.udfps_center_hint_lottie
      StageViewModel.Fingertip -> R.raw.udfps_tip_hint_lottie
      StageViewModel.LeftEdge -> R.raw.udfps_left_edge_hint_lottie
      StageViewModel.RightEdge -> R.raw.udfps_right_edge_hint_lottie
      StageViewModel.Unknown -> null
    }
  }

  private val viewModelProvider: ViewModelProvider by lazy {
    if (factory != null) {
      ViewModelProvider(requireActivity(), factory!!)
    } else {
      ViewModelProvider(requireActivity())
    }
  }

  companion object {
    private const val TAG = "UDFPSEnrollFragment"
    private val navStep = FingerprintNavigationStep.Enrollment::class
  }
}
+36 −0
Original line number Diff line number Diff line
/*
 * 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

/**
 * A view model that describes the various stages of UDFPS Enrollment. This stages typically update
 * the enrollment UI in a major way, such as changing the lottie animation or changing the location
 * of the where the user should press their fingerprint
 */
sealed class StageViewModel {
  data object Unknown : StageViewModel()

  data object Guided : StageViewModel()

  data object Center : StageViewModel()

  data object Fingertip : StageViewModel()

  data object LeftEdge : StageViewModel()

  data object RightEdge : StageViewModel()
}
+42 −0
Original line number Diff line number Diff line
/*
 * 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 com.android.settings.biometrics.fingerprint2.ui.enrollment.viewmodel.FingerprintNavigationStep
import kotlinx.coroutines.flow.flowOf

/** ViewModel used to drive UDFPS Enrollment through [UdfpsEnrollFragment] */
class UdfpsViewModel() : ViewModel() {

  /** Indicates what stage UDFPS enrollment is in. */
  val stageFlow = flowOf(StageViewModel.Center)

  class UdfpsEnrollmentFactory() : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
      return UdfpsViewModel() as T
    }
  }

  companion object {
    private val navStep = FingerprintNavigationStep.Enrollment::class
    private const val TAG = "UDFPSViewModel"
  }
}