diff options
8 files changed, 295 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index baadc66170cc..84abf57cacf2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -24,8 +24,10 @@ import android.hardware.biometrics.BiometricManager import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback import android.os.Looper import android.os.UserHandle +import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.Dumpable +import com.android.systemui.R import com.android.systemui.biometrics.AuthController import com.android.systemui.broadcast.BroadcastDispatcher 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.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.shared.model.DevicePosture import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter import javax.inject.Inject @@ -47,8 +50,10 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest @@ -82,6 +87,12 @@ interface BiometricSettingsRepository { /** Whether fingerprint feature is enabled for the current user by the DevicePolicy */ 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 @@ -98,11 +109,27 @@ constructor( @Background backgroundDispatcher: CoroutineDispatcher, biometricManager: BiometricManager?, @Main looper: Looper, + devicePostureRepository: DevicePostureRepository, dumpManager: DumpManager, ) : BiometricSettingsRepository, Dumpable { + override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> + init { 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?>) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt new file mode 100644 index 000000000000..adb1e01d0d00 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DevicePostureRepository.kt @@ -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" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 4a262f580749..f27f89995dbd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -31,6 +31,8 @@ interface KeyguardRepositoryModule { @Binds fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository + @Binds fun devicePostureRepository(impl: DevicePostureRepositoryImpl): DevicePostureRepository + @Binds fun biometricSettingsRepository( impl: BiometricSettingsRepositoryImpl diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt new file mode 100644 index 000000000000..fff7cfe1d6a3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DevicePosture.kt @@ -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 + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 21ad5e2cd311..5dc04f7efa63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest 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_REQUIRED_AFTER_BOOT +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController 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.SIDE_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.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.TestScope @@ -62,6 +66,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner::class) @@ -78,6 +83,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { private lateinit var biometricManagerCallback: ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub> private lateinit var userRepository: FakeUserRepository + private lateinit var devicePostureRepository: FakeDevicePostureRepository private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope @@ -90,6 +96,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { testDispatcher = StandardTestDispatcher() testScope = TestScope(testDispatcher) userRepository = FakeUserRepository() + devicePostureRepository = FakeDevicePostureRepository() } private suspend fun createBiometricSettingsRepository() { @@ -108,6 +115,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { looper = testableLooper!!.looper, dumpManager = dumpManager, biometricManager = biometricManager, + devicePostureRepository = devicePostureRepository, ) testScope.runCurrent() } @@ -299,6 +307,50 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { 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) { authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt new file mode 100644 index 000000000000..bd6b7a853dfc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DevicePostureRepositoryTest.kt @@ -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) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index 01dac362432d..d4b1701892c7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOf class FakeBiometricSettingsRepository : BiometricSettingsRepository { @@ -42,6 +43,9 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { override val isFingerprintEnabledByDevicePolicy = _isFingerprintEnabledByDevicePolicy.asStateFlow() + override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean> + get() = flowOf(true) + fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) { _isFingerprintEnrolled.value = isFingerprintEnrolled } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt new file mode 100644 index 000000000000..914c786a1c7f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDevicePostureRepository.kt @@ -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 + } +} |