Merge "Add repository to be the source of truth for device posture" into tm-qpr-dev

This commit is contained in:
Chandru S
2023-02-24 19:52:10 +00:00
committed by Android (Google) Code Review
8 changed files with 295 additions and 0 deletions

View File

@@ -24,8 +24,10 @@ import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.os.Looper import android.os.Looper
import android.os.UserHandle import android.os.UserHandle
import android.util.Log
import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.AuthController
import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -35,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter import java.io.PrintWriter
import javax.inject.Inject import javax.inject.Inject
@@ -47,8 +50,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
@@ -82,6 +87,12 @@ interface BiometricSettingsRepository {
/** Whether fingerprint feature is enabled for the current user by the DevicePolicy */ /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */
val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean>
/**
* Whether face authentication is supported for the current device posture. Face auth can be
* restricted to specific postures using [R.integer.config_face_auth_supported_posture]
*/
val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
} }
@SysUISingleton @SysUISingleton
@@ -98,11 +109,27 @@ constructor(
@Background backgroundDispatcher: CoroutineDispatcher, @Background backgroundDispatcher: CoroutineDispatcher,
biometricManager: BiometricManager?, biometricManager: BiometricManager?,
@Main looper: Looper, @Main looper: Looper,
devicePostureRepository: DevicePostureRepository,
dumpManager: DumpManager, dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable { ) : BiometricSettingsRepository, Dumpable {
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
init { init {
dumpManager.registerDumpable(this) dumpManager.registerDumpable(this)
val configFaceAuthSupportedPosture =
DevicePosture.toPosture(
context.resources.getInteger(R.integer.config_face_auth_supported_posture)
)
isFaceAuthSupportedInCurrentPosture =
if (configFaceAuthSupportedPosture == DevicePosture.UNKNOWN) {
flowOf(true)
} else {
devicePostureRepository.currentDevicePosture.map {
it == configFaceAuthSupportedPosture
}
}
.onEach { Log.d(TAG, "isFaceAuthSupportedInCurrentPosture value changed to: $it") }
} }
override fun dump(pw: PrintWriter, args: Array<String?>) { override fun dump(pw: PrintWriter, args: Array<String?>) {

View File

@@ -0,0 +1,58 @@
/*
* 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.systemui.keyguard.data.repository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.statusbar.policy.DevicePostureController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
/** Provide current device posture state. */
interface DevicePostureRepository {
/** Provides the current device posture. */
val currentDevicePosture: Flow<DevicePosture>
}
@SysUISingleton
class DevicePostureRepositoryImpl
@Inject
constructor(private val postureController: DevicePostureController) : DevicePostureRepository {
override val currentDevicePosture: Flow<DevicePosture>
get() = conflatedCallbackFlow {
val sendPostureUpdate = { posture: Int ->
val currentDevicePosture = DevicePosture.toPosture(posture)
trySendWithFailureLogging(
currentDevicePosture,
TAG,
"Error sending posture update to $currentDevicePosture"
)
}
val callback = DevicePostureController.Callback { sendPostureUpdate(it) }
postureController.addCallback(callback)
sendPostureUpdate(postureController.devicePosture)
awaitClose { postureController.removeCallback(callback) }
}
companion object {
const val TAG = "PostureRepositoryImpl"
}
}

View File

@@ -31,6 +31,8 @@ interface KeyguardRepositoryModule {
@Binds @Binds
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
@Binds fun devicePostureRepository(impl: DevicePostureRepositoryImpl): DevicePostureRepository
@Binds @Binds
fun biometricSettingsRepository( fun biometricSettingsRepository(
impl: BiometricSettingsRepositoryImpl impl: BiometricSettingsRepositoryImpl

View File

@@ -0,0 +1,41 @@
/*
* 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.systemui.keyguard.shared.model
import com.android.systemui.statusbar.policy.DevicePostureController
/** Represents the possible posture states of the device. */
enum class DevicePosture {
UNKNOWN,
CLOSED,
HALF_OPENED,
OPENED,
FLIPPED;
companion object {
fun toPosture(@DevicePostureController.DevicePostureInt posture: Int): DevicePosture {
return when (posture) {
DevicePostureController.DEVICE_POSTURE_CLOSED -> CLOSED
DevicePostureController.DEVICE_POSTURE_HALF_OPENED -> HALF_OPENED
DevicePostureController.DEVICE_POSTURE_OPENED -> OPENED
DevicePostureController.DEVICE_POSTURE_FLIPPED -> FLIPPED
DevicePostureController.DEVICE_POSTURE_UNKNOWN -> UNKNOWN
else -> UNKNOWN
}
}
}
}

View File

@@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectLastValue
@@ -38,11 +39,14 @@ import com.android.systemui.keyguard.data.repository.BiometricType.FACE
import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPRINT import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPRINT
import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT
import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
@@ -62,6 +66,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest @SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true) @TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@@ -78,6 +83,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
private lateinit var biometricManagerCallback: private lateinit var biometricManagerCallback:
ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub> ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
private lateinit var userRepository: FakeUserRepository private lateinit var userRepository: FakeUserRepository
private lateinit var devicePostureRepository: FakeDevicePostureRepository
private lateinit var testDispatcher: TestDispatcher private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope private lateinit var testScope: TestScope
@@ -90,6 +96,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
testDispatcher = StandardTestDispatcher() testDispatcher = StandardTestDispatcher()
testScope = TestScope(testDispatcher) testScope = TestScope(testDispatcher)
userRepository = FakeUserRepository() userRepository = FakeUserRepository()
devicePostureRepository = FakeDevicePostureRepository()
} }
private suspend fun createBiometricSettingsRepository() { private suspend fun createBiometricSettingsRepository() {
@@ -108,6 +115,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
looper = testableLooper!!.looper, looper = testableLooper!!.looper,
dumpManager = dumpManager, dumpManager = dumpManager,
biometricManager = biometricManager, biometricManager = biometricManager,
devicePostureRepository = devicePostureRepository,
) )
testScope.runCurrent() testScope.runCurrent()
} }
@@ -299,6 +307,50 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any()) verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
} }
@Test
fun faceAuthIsAlwaysSupportedIfSpecificPostureIsNotConfigured() =
testScope.runTest {
overrideResource(
R.integer.config_face_auth_supported_posture,
DevicePostureController.DEVICE_POSTURE_UNKNOWN
)
createBiometricSettingsRepository()
assertThat(collectLastValue(underTest.isFaceAuthSupportedInCurrentPosture)()).isTrue()
}
@Test
fun faceAuthIsSupportedOnlyWhenDevicePostureMatchesConfigValue() =
testScope.runTest {
overrideResource(
R.integer.config_face_auth_supported_posture,
DevicePostureController.DEVICE_POSTURE_FLIPPED
)
createBiometricSettingsRepository()
val isFaceAuthSupported =
collectLastValue(underTest.isFaceAuthSupportedInCurrentPosture)
assertThat(isFaceAuthSupported()).isFalse()
devicePostureRepository.setCurrentPosture(DevicePosture.CLOSED)
assertThat(isFaceAuthSupported()).isFalse()
devicePostureRepository.setCurrentPosture(DevicePosture.HALF_OPENED)
assertThat(isFaceAuthSupported()).isFalse()
devicePostureRepository.setCurrentPosture(DevicePosture.OPENED)
assertThat(isFaceAuthSupported()).isFalse()
devicePostureRepository.setCurrentPosture(DevicePosture.UNKNOWN)
assertThat(isFaceAuthSupported()).isFalse()
devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
assertThat(isFaceAuthSupported()).isTrue()
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
} }

View File

@@ -0,0 +1,80 @@
/*
* 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.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class DevicePostureRepositoryTest : SysuiTestCase() {
private lateinit var underTest: DevicePostureRepository
private lateinit var testScope: TestScope
@Mock private lateinit var devicePostureController: DevicePostureController
@Captor private lateinit var callback: ArgumentCaptor<DevicePostureController.Callback>
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
testScope = TestScope()
underTest = DevicePostureRepositoryImpl(postureController = devicePostureController)
}
@Test
fun postureChangesArePropagated() =
testScope.runTest {
whenever(devicePostureController.devicePosture)
.thenReturn(DevicePostureController.DEVICE_POSTURE_FLIPPED)
val currentPosture = collectLastValue(underTest.currentDevicePosture)
assertThat(currentPosture()).isEqualTo(DevicePosture.FLIPPED)
verify(devicePostureController).addCallback(callback.capture())
callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_UNKNOWN)
assertThat(currentPosture()).isEqualTo(DevicePosture.UNKNOWN)
callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED)
assertThat(currentPosture()).isEqualTo(DevicePosture.CLOSED)
callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED)
assertThat(currentPosture()).isEqualTo(DevicePosture.HALF_OPENED)
callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED)
assertThat(currentPosture()).isEqualTo(DevicePosture.OPENED)
callback.value.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED)
assertThat(currentPosture()).isEqualTo(DevicePosture.FLIPPED)
}
}

View File

@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
class FakeBiometricSettingsRepository : BiometricSettingsRepository { class FakeBiometricSettingsRepository : BiometricSettingsRepository {
@@ -42,6 +43,9 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
override val isFingerprintEnabledByDevicePolicy = override val isFingerprintEnabledByDevicePolicy =
_isFingerprintEnabledByDevicePolicy.asStateFlow() _isFingerprintEnabledByDevicePolicy.asStateFlow()
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
get() = flowOf(true)
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) { fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled _isFingerprintEnrolled.value = isFingerprintEnrolled
} }

View File

@@ -0,0 +1,31 @@
/*
* 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.systemui.keyguard.data.repository
import com.android.systemui.keyguard.shared.model.DevicePosture
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeDevicePostureRepository : DevicePostureRepository {
private val _currentDevicePosture = MutableStateFlow(DevicePosture.UNKNOWN)
override val currentDevicePosture: Flow<DevicePosture>
get() = _currentDevicePosture
fun setCurrentPosture(posture: DevicePosture) {
_currentDevicePosture.value = posture
}
}