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

Commit a98dc8d4 authored by Joshua McCloskey's avatar Joshua McCloskey
Browse files

Rear Fingerprint Enrollment

Bug: 297083009
Test: atest RFPSIconTouchViewModelTest FingerprintEnrollEnrollingViewModelTest FingerprintManagerInteractorTest
Change-Id: Icc072e7d7815070087ccb50ea5937c386b06fb11
parent f29e758d
Loading
Loading
Loading
Loading
+75 −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"
    style="?attr/fingerprint_layout_theme"
    android:id="@+id/setup_wizard_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    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"
        android:orientation="vertical">

        <com.google.android.setupdesign.view.FillContentLayout
            android:layout_width="@dimen/fingerprint_progress_bar_max_size"
            android:layout_height="@dimen/fingerprint_progress_bar_max_size"
            android:layout_marginVertical="24dp"
            android:paddingTop="0dp"
            android:paddingBottom="0dp">

            <com.android.settings.biometrics.fingerprint2.ui.enrollment.modules.enrolling.rfps.ui.widget.RFPSProgressBar
                xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/fingerprint_progress_bar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@drawable/fp_illustration"
                android:minHeight="@dimen/fingerprint_progress_bar_min_size"
                android:progress="0" />

        </com.google.android.setupdesign.view.FillContentLayout>

        <TextView
            android:id="@+id/text"
            style="@style/TextAppearance.ErrorText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal|bottom"
            android:accessibilityLiveRegion="polite"
            android:gravity="center"
            android:visibility="invisible" />

    </LinearLayout>

</LinearLayout>

</com.google.android.setupdesign.GlifLayout>
 No newline at end of file
+52 −5
Original line number Diff line number Diff line
@@ -16,14 +16,61 @@

package com.android.settings.biometrics.fingerprint2.conversion

import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.R
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState

class Util

object Util {
  fun EnrollReason.toOriginalReason(): Int {
    return when (this) {
      EnrollReason.EnrollEnrolling -> FingerprintManager.ENROLL_ENROLL
      EnrollReason.FindSensor -> FingerprintManager.ENROLL_FIND_SENSOR
    }
  }

  fun Int.toEnrollError(isSetupWizard: Boolean): FingerEnrollState.EnrollError {
    val errTitle =
      when (this) {
        FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
          R.string.security_settings_fingerprint_enroll_error_dialog_title
        FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
          R.string.security_settings_fingerprint_bad_calibration_title
        else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
      }
    val errString =
      if (isSetupWizard) {
        when (this) {
          FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
            R.string.security_settings_fingerprint_enroll_error_dialog_title
          FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
            R.string.security_settings_fingerprint_bad_calibration_title
          else -> R.string.security_settings_fingerprint_enroll_error_unable_to_process_dialog_title
        }
      } else {
        when (this) {
          // This message happens when the underlying crypto layer
          // decides to revoke the enrollment auth token
          FingerprintManager.FINGERPRINT_ERROR_TIMEOUT ->
            R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message
          FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION ->
            R.string.security_settings_fingerprint_bad_calibration
          FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS ->
            R.string.security_settings_fingerprint_enroll_error_unable_to_process_message
          // There's nothing specific to tell the user about. Ask them to try again.
          else -> R.string.security_settings_fingerprint_enroll_error_generic_dialog_message
        }
      }

    return FingerEnrollState.EnrollError(
      errTitle,
      errString,
      this == FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
      this == FINGERPRINT_ERROR_CANCELED,
    )
  }

}
+52 −18
Original line number Diff line number Diff line
@@ -24,12 +24,16 @@ import android.hardware.fingerprint.FingerprintManager.RemovalCallback
import android.os.CancellationSignal
import android.util.Log
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.conversion.toOriginalReason
import com.android.settings.biometrics.fingerprint2.conversion.Util.toEnrollError
import com.android.settings.biometrics.fingerprint2.conversion.Util.toOriginalReason
import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider
import com.android.settings.biometrics.fingerprint2.shared.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.shared.model.EnrollReason
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollStateViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintViewModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintFlow
import com.android.settings.biometrics.fingerprint2.shared.model.FingerEnrollState
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintAuthAttemptModel
import com.android.settings.biometrics.fingerprint2.shared.model.FingerprintData
import com.android.settings.biometrics.fingerprint2.shared.model.SetupWizard
import com.android.settings.password.ChooseLockSettingsHelper
import com.android.systemui.biometrics.shared.model.toFingerprintSensor
import kotlin.coroutines.resume
@@ -38,9 +42,12 @@ import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext

@@ -51,7 +58,8 @@ class FingerprintManagerInteractorImpl(
  private val backgroundDispatcher: CoroutineDispatcher,
  private val fingerprintManager: FingerprintManager,
  private val gatekeeperPasswordProvider: GatekeeperPasswordProvider,
  private val pressToAuthProvider: () -> Boolean,
  private val pressToAuthProvider: PressToAuthProvider,
  private val fingerprintFlow: FingerprintFlow,
) : FingerprintManagerInteractor {

  private val maxFingerprints =
@@ -60,6 +68,8 @@ class FingerprintManagerInteractorImpl(
    )
  private val applicationContext = applicationContext.applicationContext

  private val enrollRequestOutstanding = MutableStateFlow(false)

  override suspend fun generateChallenge(gateKeeperPasswordHandle: Long): Pair<Long, ByteArray> =
    suspendCoroutine {
      val callback = GenerateChallengeCallback { _, userId, challenge ->
@@ -75,11 +85,11 @@ class FingerprintManagerInteractorImpl(
      fingerprintManager.generateChallenge(applicationContext.userId, callback)
    }

  override val enrolledFingerprints: Flow<List<FingerprintViewModel>> = flow {
  override val enrolledFingerprints: Flow<List<FingerprintData>> = flow {
    emit(
      fingerprintManager
        .getEnrolledFingerprints(applicationContext.userId)
        .map { (FingerprintViewModel(it.name.toString(), it.biometricId, it.deviceId)) }
        .map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
        .toList()
    )
  }
@@ -103,28 +113,51 @@ class FingerprintManagerInteractorImpl(
  override suspend fun enroll(
    hardwareAuthToken: ByteArray?,
    enrollReason: EnrollReason,
  ): Flow<FingerEnrollStateViewModel> = callbackFlow {
  ): Flow<FingerEnrollState> = callbackFlow {
    // TODO (b/308456120) Improve this logic
    if (enrollRequestOutstanding.value) {
      Log.d(TAG, "Outstanding enroll request, waiting 150ms")
      delay(150)
      if (enrollRequestOutstanding.value) {
        Log.e(TAG, "Request still present, continuing")
      }
    }

    enrollRequestOutstanding.update { true }

    var streamEnded = false
    var totalSteps: Int? = null
    val enrollmentCallback =
      object : FingerprintManager.EnrollmentCallback() {
        override fun onEnrollmentProgress(remaining: Int) {
          trySend(FingerEnrollStateViewModel.EnrollProgress(remaining)).onFailure { error ->
          // This is sort of an implementation detail, but unfortunately the API isn't
          // very expressive. If anything we should look at changing the FingerprintManager API.
          if (totalSteps == null) {
            totalSteps = remaining + 1
          }

          trySend(FingerEnrollState.EnrollProgress(remaining, totalSteps!!)).onFailure {
            error ->
            Log.d(TAG, "onEnrollmentProgress($remaining) failed to send, due to $error")
          }

          if (remaining == 0) {
            streamEnded = true
            enrollRequestOutstanding.update { false }
          }
        }

        override fun onEnrollmentHelp(helpMsgId: Int, helpString: CharSequence?) {
          trySend(FingerEnrollStateViewModel.EnrollHelp(helpMsgId, helpString.toString()))
          trySend(FingerEnrollState.EnrollHelp(helpMsgId, helpString.toString()))
            .onFailure { error -> Log.d(TAG, "onEnrollmentHelp failed to send, due to $error") }
        }

        override fun onEnrollmentError(errMsgId: Int, errString: CharSequence?) {
          trySend(FingerEnrollStateViewModel.EnrollError(errMsgId, errString.toString()))
          trySend(errMsgId.toEnrollError(fingerprintFlow == SetupWizard))
            .onFailure { error -> Log.d(TAG, "onEnrollmentError failed to send, due to $error") }
          Log.d(TAG, "onEnrollmentError($errMsgId)")
          streamEnded = true
          enrollRequestOutstanding.update { false }
        }
      }

@@ -140,12 +173,13 @@ class FingerprintManagerInteractorImpl(
      // If the stream has not been ended, and the user has stopped collecting the flow
      // before it was over, send cancel.
      if (!streamEnded) {
        Log.e(TAG, "Cancel is sent from settings for enroll()")
        cancellationSignal.cancel()
      }
    }
  }

  override suspend fun removeFingerprint(fp: FingerprintViewModel): Boolean = suspendCoroutine {
  override suspend fun removeFingerprint(fp: FingerprintData): Boolean = suspendCoroutine {
    val callback =
      object : RemovalCallback() {
        override fun onRemovalError(
@@ -170,7 +204,7 @@ class FingerprintManagerInteractorImpl(
    )
  }

  override suspend fun renameFingerprint(fp: FingerprintViewModel, newName: String) {
  override suspend fun renameFingerprint(fp: FingerprintData, newName: String) {
    withContext(backgroundDispatcher) {
      fingerprintManager.rename(fp.fingerId, applicationContext.userId, newName)
    }
@@ -181,11 +215,11 @@ class FingerprintManagerInteractorImpl(
  }

  override suspend fun pressToAuthEnabled(): Boolean = suspendCancellableCoroutine {
    it.resume(pressToAuthProvider())
    it.resume(pressToAuthProvider.isEnabled)
  }

  override suspend fun authenticate(): FingerprintAuthAttemptViewModel =
    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptViewModel> ->
  override suspend fun authenticate(): FingerprintAuthAttemptModel =
    suspendCancellableCoroutine { c: CancellableContinuation<FingerprintAuthAttemptModel> ->
      val authenticationCallback =
        object : FingerprintManager.AuthenticationCallback() {

@@ -195,7 +229,7 @@ class FingerprintManagerInteractorImpl(
              Log.d(TAG, "framework sent down onAuthError after finish")
              return
            }
            c.resume(FingerprintAuthAttemptViewModel.Error(errorCode, errString.toString()))
            c.resume(FingerprintAuthAttemptModel.Error(errorCode, errString.toString()))
          }

          override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
@@ -204,7 +238,7 @@ class FingerprintManagerInteractorImpl(
              Log.d(TAG, "framework sent down onAuthError after finish")
              return
            }
            c.resume(FingerprintAuthAttemptViewModel.Success(result.fingerprint?.biometricId ?: -1))
            c.resume(FingerprintAuthAttemptModel.Success(result.fingerprint?.biometricId ?: -1))
          }
        }

+51 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.biometrics.fingerprint2.repository

import android.content.Context
import android.provider.Settings
import com.android.settings.biometrics.fingerprint2.shared.data.repository.PressToAuthProvider

class PressToAuthProviderImpl(val context: Context) : PressToAuthProvider {
  override val isEnabled: Boolean
    get() {
      var toReturn: Int =
        Settings.Secure.getIntForUser(
          context.contentResolver,
          Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
          -1,
          context.userId,
        )
      if (toReturn == -1) {
        toReturn =
          if (
            context.resources.getBoolean(com.android.internal.R.bool.config_performantAuthDefault)
          ) {
            1
          } else {
            0
          }
        Settings.Secure.putIntForUser(
          context.contentResolver,
          Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED,
          toReturn,
          context.userId
        )
      }
      return (toReturn == 1)
    }
}
+27 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.biometrics.fingerprint2.shared.data.repository

/**
 * Interface that indicates if press to auth is on or off.
 */
interface PressToAuthProvider {
    /**
     * Indicates true if the PressToAuth feature is enabled, false otherwise.
     */
    val isEnabled: Boolean
}
 No newline at end of file
Loading