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

Commit ff69c0ca authored by Joshua Mccloskey's avatar Joshua Mccloskey Committed by Android (Google) Code Review
Browse files

Merge "Rear Fingerprint Enrollment" into main

parents 1dfa0825 a98dc8d4
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