Refactor biometric prompt credential screen - data layer.
Step 1/3, add repo layer for global application state of biometric prompt. Bug: 251476085 Test: atest PromptRepositoryImplTest Change-Id: Ide096c435b58ece7962f8a5b6d1c3af992885554
This commit is contained in:
@@ -1068,6 +1068,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
||||
return mUdfpsEnrolledForUser.get(userId);
|
||||
}
|
||||
|
||||
/** If BiometricPrompt is currently being shown to the user. */
|
||||
public boolean isShowing() {
|
||||
return mCurrentDialog != null;
|
||||
}
|
||||
|
||||
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
|
||||
mCurrentDialogArgs = args;
|
||||
|
||||
|
||||
@@ -16,32 +16,39 @@
|
||||
|
||||
package com.android.systemui.biometrics.dagger
|
||||
|
||||
import com.android.systemui.biometrics.data.repository.PromptRepository
|
||||
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.util.concurrency.ThreadFactory
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Qualifier
|
||||
|
||||
/**
|
||||
* Dagger module for all things biometric.
|
||||
*/
|
||||
/** Dagger module for all things biometric. */
|
||||
@Module
|
||||
object BiometricsModule {
|
||||
interface BiometricsModule {
|
||||
|
||||
/** Background [Executor] for HAL related operations. */
|
||||
@Provides
|
||||
@Binds
|
||||
@SysUISingleton
|
||||
@JvmStatic
|
||||
@BiometricsBackground
|
||||
fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
|
||||
threadFactory.buildExecutorOnNewThread("biometrics")
|
||||
fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
|
||||
|
||||
companion object {
|
||||
/** Background [Executor] for HAL related operations. */
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@JvmStatic
|
||||
@BiometricsBackground
|
||||
fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
|
||||
threadFactory.buildExecutorOnNewThread("biometrics")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Background executor for HAL operations that are latency sensitive but too
|
||||
* slow to run on the main thread. Prefer the shared executors, such as
|
||||
* [com.android.systemui.dagger.qualifiers.Background] when a HAL is not directly involved.
|
||||
* Background executor for HAL operations that are latency sensitive but too slow to run on the main
|
||||
* thread. Prefer the shared executors, such as [com.android.systemui.dagger.qualifiers.Background]
|
||||
* when a HAL is not directly involved.
|
||||
*/
|
||||
@Qualifier
|
||||
@MustBeDocumented
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.systemui.biometrics.data.model
|
||||
|
||||
import com.android.systemui.biometrics.Utils
|
||||
|
||||
// TODO(b/251476085): this should eventually replace Utils.CredentialType
|
||||
/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
|
||||
enum class PromptKind {
|
||||
ANY_BIOMETRIC,
|
||||
PIN,
|
||||
PATTERN,
|
||||
PASSWORD,
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.android.systemui.biometrics.data.repository
|
||||
|
||||
import android.hardware.biometrics.PromptInfo
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.biometrics.data.model.PromptKind
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* A repository for the global state of BiometricPrompt.
|
||||
*
|
||||
* There is never more than one instance of the prompt at any given time.
|
||||
*/
|
||||
interface PromptRepository {
|
||||
|
||||
/** If the prompt is showing. */
|
||||
val isShowing: Flow<Boolean>
|
||||
|
||||
/** The app-specific details to show in the prompt. */
|
||||
val promptInfo: StateFlow<PromptInfo?>
|
||||
|
||||
/** The user that the prompt is for. */
|
||||
val userId: StateFlow<Int?>
|
||||
|
||||
/** The gatekeeper challenge, if one is associated with this prompt. */
|
||||
val challenge: StateFlow<Long?>
|
||||
|
||||
/** The kind of credential to use (biometric, pin, pattern, etc.). */
|
||||
val kind: StateFlow<PromptKind>
|
||||
|
||||
/** Update the prompt configuration, which should be set before [isShowing]. */
|
||||
fun setPrompt(
|
||||
promptInfo: PromptInfo,
|
||||
userId: Int,
|
||||
gatekeeperChallenge: Long?,
|
||||
kind: PromptKind = PromptKind.ANY_BIOMETRIC,
|
||||
)
|
||||
|
||||
/** Unset the prompt info. */
|
||||
fun unsetPrompt()
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
|
||||
PromptRepository {
|
||||
|
||||
override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : AuthController.Callback {
|
||||
override fun onBiometricPromptShown() =
|
||||
trySendWithFailureLogging(true, TAG, "set isShowing")
|
||||
|
||||
override fun onBiometricPromptDismissed() =
|
||||
trySendWithFailureLogging(false, TAG, "unset isShowing")
|
||||
}
|
||||
authController.addCallback(callback)
|
||||
trySendWithFailureLogging(authController.isShowing, TAG, "update isShowing")
|
||||
awaitClose { authController.removeCallback(callback) }
|
||||
}
|
||||
|
||||
private val _promptInfo: MutableStateFlow<PromptInfo?> = MutableStateFlow(null)
|
||||
override val promptInfo = _promptInfo.asStateFlow()
|
||||
|
||||
private val _challenge: MutableStateFlow<Long?> = MutableStateFlow(null)
|
||||
override val challenge: StateFlow<Long?> = _challenge.asStateFlow()
|
||||
|
||||
private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
|
||||
override val userId = _userId.asStateFlow()
|
||||
|
||||
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
|
||||
override val kind = _kind.asStateFlow()
|
||||
|
||||
override fun setPrompt(
|
||||
promptInfo: PromptInfo,
|
||||
userId: Int,
|
||||
gatekeeperChallenge: Long?,
|
||||
kind: PromptKind,
|
||||
) {
|
||||
_kind.value = kind
|
||||
_userId.value = userId
|
||||
_challenge.value = gatekeeperChallenge
|
||||
_promptInfo.value = promptInfo
|
||||
}
|
||||
|
||||
override fun unsetPrompt() {
|
||||
_promptInfo.value = null
|
||||
_userId.value = null
|
||||
_challenge.value = null
|
||||
_kind.value = PromptKind.ANY_BIOMETRIC
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BiometricPromptRepository"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.android.systemui.biometrics.data.repository
|
||||
|
||||
import android.hardware.biometrics.PromptInfo
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.biometrics.data.model.PromptKind
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class PromptRepositoryImplTest : SysuiTestCase() {
|
||||
|
||||
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
|
||||
|
||||
@Mock private lateinit var authController: AuthController
|
||||
|
||||
private lateinit var repository: PromptRepositoryImpl
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
repository = PromptRepositoryImpl(authController)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isShowing() = runBlockingTest {
|
||||
whenever(authController.isShowing).thenReturn(true)
|
||||
|
||||
val values = mutableListOf<Boolean>()
|
||||
val job = launch { repository.isShowing.toList(values) }
|
||||
assertThat(values).containsExactly(true)
|
||||
|
||||
withArgCaptor<AuthController.Callback> {
|
||||
verify(authController).addCallback(capture())
|
||||
|
||||
value.onBiometricPromptShown()
|
||||
assertThat(values).containsExactly(true, true)
|
||||
|
||||
value.onBiometricPromptDismissed()
|
||||
assertThat(values).containsExactly(true, true, false).inOrder()
|
||||
|
||||
job.cancel()
|
||||
verify(authController).removeCallback(eq(value))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setsAndUnsetsPrompt() = runBlockingTest {
|
||||
val kind = PromptKind.PIN
|
||||
val uid = 8
|
||||
val challenge = 90L
|
||||
val promptInfo = PromptInfo()
|
||||
|
||||
repository.setPrompt(promptInfo, uid, challenge, kind)
|
||||
|
||||
assertThat(repository.kind.value).isEqualTo(kind)
|
||||
assertThat(repository.userId.value).isEqualTo(uid)
|
||||
assertThat(repository.challenge.value).isEqualTo(challenge)
|
||||
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
|
||||
|
||||
repository.unsetPrompt()
|
||||
|
||||
assertThat(repository.promptInfo.value).isNull()
|
||||
assertThat(repository.userId.value).isNull()
|
||||
assertThat(repository.challenge.value).isNull()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.android.systemui.biometrics.data.repository
|
||||
|
||||
import android.hardware.biometrics.PromptInfo
|
||||
import com.android.systemui.biometrics.data.model.PromptKind
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/** Fake implementation of [PromptRepository] for tests. */
|
||||
class FakePromptRepository : PromptRepository {
|
||||
|
||||
private val _isShowing = MutableStateFlow(false)
|
||||
override val isShowing = _isShowing.asStateFlow()
|
||||
|
||||
private val _promptInfo = MutableStateFlow<PromptInfo?>(null)
|
||||
override val promptInfo = _promptInfo.asStateFlow()
|
||||
|
||||
private val _userId = MutableStateFlow<Int?>(null)
|
||||
override val userId = _userId.asStateFlow()
|
||||
|
||||
private var _challenge = MutableStateFlow<Long?>(null)
|
||||
override val challenge = _challenge.asStateFlow()
|
||||
|
||||
private val _kind = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
|
||||
override val kind = _kind.asStateFlow()
|
||||
|
||||
override fun setPrompt(
|
||||
promptInfo: PromptInfo,
|
||||
userId: Int,
|
||||
gatekeeperChallenge: Long?,
|
||||
kind: PromptKind
|
||||
) {
|
||||
_promptInfo.value = promptInfo
|
||||
_userId.value = userId
|
||||
_challenge.value = gatekeeperChallenge
|
||||
_kind.value = kind
|
||||
}
|
||||
|
||||
override fun unsetPrompt() {
|
||||
_promptInfo.value = null
|
||||
_userId.value = null
|
||||
_challenge.value = null
|
||||
_kind.value = PromptKind.ANY_BIOMETRIC
|
||||
}
|
||||
|
||||
fun setIsShowing(showing: Boolean) {
|
||||
_isShowing.value = showing
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user