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

Commit 882e1c36 authored by Joshua McCloskey's avatar Joshua McCloskey
Browse files

Split up FingerprintManagerInteractor

Test: atest, screenshot tests passed
Flag: com.android.settings.flags.fingerprint_v2_enrollment
Change-Id: I70833d5d9888f730233a9757589ce7faa45eccc9
parent 59f11d93
Loading
Loading
Loading
Loading
+16 −4
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.settings;

import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.hardware.fingerprint.FingerprintManager;
import android.net.Uri;
import android.provider.Settings;
import android.util.FeatureFlagUtils;
@@ -74,9 +76,6 @@ public class SettingsApplication extends Application {

        // Set Spa environment.
        setSpaEnvironment();
        if (Flags.fingerprintV2Enrollment()) {
            mBiometricsEnvironment = new BiometricsEnvironment(this);
        }

        if (ActivityEmbeddingUtils.isSettingsSplitEnabled(this)
                && FeatureFlagUtils.isEnabled(this,
@@ -120,7 +119,20 @@ public class SettingsApplication extends Application {

    @Nullable
    public BiometricsEnvironment getBiometricEnvironment() {
        if (Flags.fingerprintV2Enrollment()) {
            if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
                final FingerprintManager fpm = getSystemService(FingerprintManager.class);
                if (mBiometricsEnvironment == null) {
                    mBiometricsEnvironment = new BiometricsEnvironment(this, fpm);
                }
                return  mBiometricsEnvironment;

            } else {
                return null;
            }

        }
        return null;
    }

    @Override
+54 −33
Original line number Diff line number Diff line
@@ -16,12 +16,9 @@

package com.android.settings.biometrics.fingerprint2

import android.content.pm.PackageManager
import android.hardware.fingerprint.FingerprintManager
import android.os.ServiceManager.ServiceNotFoundException
import android.view.MotionEvent
import android.view.accessibility.AccessibilityManager
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import com.android.internal.widget.LockPatternUtils
@@ -29,33 +26,47 @@ import com.android.settings.SettingsApplication
import com.android.settings.biometrics.GatekeeperPasswordProvider
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepository
import com.android.settings.biometrics.fingerprint2.data.repository.DebuggingRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintEnrollmentRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepository
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSensorRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.FingerprintSettingsRepositoryImpl
import com.android.settings.biometrics.fingerprint2.data.repository.UserRepoImpl
import com.android.settings.biometrics.fingerprint2.debug.data.repository.UdfpsEnrollDebugRepositoryImpl
import com.android.settings.biometrics.fingerprint2.debug.domain.interactor.DebugTouchEventInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.AccessibilityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.AuthenticateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.CanEnrollFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DebuggingInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.DisplayDensityInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollFingerprintInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrollStageInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintManagerInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.EnrolledFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FingerprintSensorInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.FoldStateInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.GenerateChallengeInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.OrientationInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RemoveFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.RenameFingerprintsInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.SensorInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.TouchEventInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.UdfpsEnrollInteractorImpl
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractor
import com.android.settings.biometrics.fingerprint2.domain.interactor.VibrationInteractorImpl
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.FingerprintManagerInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.AuthenitcateInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.CanEnrollFingerprintsInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.EnrollFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.GenerateChallengeInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RemoveFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.RenameFingerprintInteractor
import com.android.settings.biometrics.fingerprint2.lib.domain.interactor.SensorInteractor
import com.android.settings.biometrics.fingerprint2.lib.model.Settings
import java.util.concurrent.Executors
import kotlinx.coroutines.MainScope
@@ -70,43 +81,53 @@ import kotlinx.coroutines.flow.flowOf
 * This code is instantiated within the [SettingsApplication], all repos should be private &
 * immutable and all interactors should public and immutable
 */
class BiometricsEnvironment(context: SettingsApplication) : ViewModelStoreOwner {

class BiometricsEnvironment(
  val context: SettingsApplication,
  private val fingerprintManager: FingerprintManager,
) : ViewModelStoreOwner {
  private val executorService = Executors.newSingleThreadExecutor()
  private val backgroundDispatcher = executorService.asCoroutineDispatcher()
  private val applicationScope = MainScope()
  private val gateKeeperPasswordProvider = GatekeeperPasswordProvider(LockPatternUtils(context))
  private val fingerprintManager = try {
    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
      context.getSystemService(FragmentActivity.FINGERPRINT_SERVICE) as FingerprintManager?
    } else {
      null
    }
  } catch (exception: ServiceNotFoundException){
    null
  }

  private val userRepo = UserRepoImpl(context.userId)
  private val fingerprintSettingsRepository =
    FingerprintSettingsRepositoryImpl(
      context.resources.getInteger(
        com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser
      )
    )
  private val fingerprintEnrollmentRepository =
    FingerprintEnrollmentRepositoryImpl(fingerprintManager, userRepo, fingerprintSettingsRepository,
      backgroundDispatcher, applicationScope)
  private val fingerprintSensorRepository: FingerprintSensorRepository =
    FingerprintSensorRepositoryImpl(fingerprintManager, backgroundDispatcher, applicationScope)
  private val debuggingRepository: DebuggingRepository = DebuggingRepositoryImpl()
  private val udfpsDebugRepo = UdfpsEnrollDebugRepositoryImpl()

  /** For now, interactors are public to those with access to the [BiometricsEnvironment] class */
  val fingerprintEnrollInteractor: FingerprintEnrollInteractor by lazy {
    FingerprintEnrollInteractorImpl(context, fingerprintManager, Settings)
  }
  fun createSensorPropertiesInteractor(): SensorInteractor =
    SensorInteractorImpl(fingerprintSensorRepository)

  /** [FingerprintManagerInteractor] to be used to construct view models */
  val fingerprintManagerInteractor: FingerprintManagerInteractor by lazy {
    FingerprintManagerInteractorImpl(
      context,
      backgroundDispatcher,
      fingerprintManager,
      fingerprintSensorRepository,
      gateKeeperPasswordProvider,
      fingerprintEnrollInteractor,
    )
  }
  fun createCanEnrollFingerprintsInteractor(): CanEnrollFingerprintsInteractor =
    CanEnrollFingerprintsInteractorImpl(fingerprintEnrollmentRepository)

  fun createGenerateChallengeInteractor(): GenerateChallengeInteractor =
    GenerateChallengeInteractorImpl(fingerprintManager, context.userId, gateKeeperPasswordProvider)

  fun createFingerprintEnrollInteractor(): EnrollFingerprintInteractor =
    EnrollFingerprintInteractorImpl(context.userId, fingerprintManager, Settings)

  fun createFingerprintsEnrolledInteractor(): EnrolledFingerprintsInteractorImpl =
    EnrolledFingerprintsInteractorImpl(fingerprintManager, context.userId)

  fun createAuthenticateInteractor(): AuthenitcateInteractor =
    AuthenticateInteractorImpl(fingerprintManager, context.userId)

  fun createRemoveFingerprintInteractor(): RemoveFingerprintInteractor =
    RemoveFingerprintsInteractorImpl(fingerprintManager, context.userId)

  fun createRenameFingerprintInteractor(): RenameFingerprintInteractor =
    RenameFingerprintsInteractorImpl(fingerprintManager, context.userId, backgroundDispatcher)

  val accessibilityInteractor: AccessibilityInteractor by lazy {
    AccessibilityInteractorImpl(
+98 −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.data.repository

import android.hardware.biometrics.BiometricStateListener
import android.hardware.fingerprint.FingerprintManager
import com.android.settings.biometrics.fingerprint2.lib.model.FingerprintData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

/** Repository that contains information about fingerprint enrollments. */
interface FingerprintEnrollmentRepository {
  /** The current enrollments of the user */
  val currentEnrollments: Flow<List<FingerprintData>?>

  /** Indicates if a user can enroll another fingerprint */
  val canEnrollUser: Flow<Boolean>

  fun maxFingerprintsEnrollable(): Int
}

class FingerprintEnrollmentRepositoryImpl(
  fingerprintManager: FingerprintManager,
  userRepo: UserRepo,
  private val settingsRepository: FingerprintSettingsRepository,
  backgroundDispatcher: CoroutineDispatcher,
  applicationScope: CoroutineScope,
) : FingerprintEnrollmentRepository {

  private val enrollmentChangedFlow: Flow<Int?> =
    callbackFlow {
        val callback =
          object : BiometricStateListener() {
            override fun onEnrollmentsChanged(userId: Int, sensorId: Int, hasEnrollments: Boolean) {
              trySend(userId)
            }
          }
        withContext(backgroundDispatcher) {
          fingerprintManager.registerBiometricStateListener(callback)
        }
        awaitClose {
          // no way to unregister
        }
      }
      .stateIn(applicationScope, started = SharingStarted.Eagerly, initialValue = null)

  override val currentEnrollments: Flow<List<FingerprintData>> =
    userRepo.currentUser
      .distinctUntilChanged()
      .flatMapLatest { currentUser ->
        enrollmentChangedFlow.map { enrollmentChanged ->
          if (enrollmentChanged == null || enrollmentChanged == currentUser) {
            fingerprintManager
              .getEnrolledFingerprints(currentUser)
              ?.map { (FingerprintData(it.name.toString(), it.biometricId, it.deviceId)) }
              ?.toList()
          } else {
            null
          }
        }
      }
      .filterNotNull()
      .flowOn(backgroundDispatcher)

  override val canEnrollUser: Flow<Boolean> =
    currentEnrollments.map {
      it?.size?.let { it < settingsRepository.maxEnrollableFingerprints() } ?: false
    }

  override fun maxFingerprintsEnrollable(): Int {
    return settingsRepository.maxEnrollableFingerprints()
  }
}
+10 −2
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.withContext
@@ -43,10 +45,13 @@ import kotlinx.coroutines.withContext
interface FingerprintSensorRepository {
  /** Get the [FingerprintSensor] */
  val fingerprintSensor: Flow<FingerprintSensor>

  /** Indicates if this device supports the side fingerprint sensor */
  val hasSideFps: Flow<Boolean>
}

class FingerprintSensorRepositoryImpl(
  fingerprintManager: FingerprintManager?,
  private val fingerprintManager: FingerprintManager,
  backgroundDispatcher: CoroutineDispatcher,
  activityScope: CoroutineScope,
) : FingerprintSensorRepository {
@@ -66,7 +71,7 @@ class FingerprintSensorRepositoryImpl(
            }
          }
        withContext(backgroundDispatcher) {
          fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
          fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
        }
        awaitClose {}
      }
@@ -75,6 +80,9 @@ class FingerprintSensorRepositoryImpl(
  override val fingerprintSensor: Flow<FingerprintSensor> =
    fingerprintPropsInternal.transform { emit(it.toFingerprintSensor()) }

  override val hasSideFps: Flow<Boolean> =
    fingerprintSensor.flatMapLatest { flow { emit(fingerprintManager.isPowerbuttonFps()) } }

  companion object {

    private val DEFAULT_PROPS =
+32 −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.data.repository

/**
 * Repository for storing metadata about fingerprint enrollments.
 */
interface FingerprintSettingsRepository {
    /**
     * Indicates the maximum number of fingerprints enrollable
     */
    fun maxEnrollableFingerprints(): Int
}

class FingerprintSettingsRepositoryImpl(private val maxFingerprintsEnrollable: Int) :
    FingerprintSettingsRepository {
    override fun maxEnrollableFingerprints() = maxFingerprintsEnrollable
}
Loading