diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index b965ac97cc1c6..ff1b31d8848f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -30,6 +30,9 @@ import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.tuner.TunerService import java.io.PrintWriter @@ -40,11 +43,19 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr private val mKeyguardStateController: KeyguardStateController private val statusBarStateController: StatusBarStateController + private val devicePostureController: DevicePostureController @BypassOverride private val bypassOverride: Int private var hasFaceFeature: Boolean + @DevicePostureInt private val configFaceAuthSupportedPosture: Int + @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN private var pendingUnlock: PendingUnlock? = null private val listeners = mutableListOf() - + private val postureCallback = DevicePostureController.Callback { posture -> + if (postureState != posture) { + postureState = posture + notifyListeners() + } + } private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback { override fun onFaceAuthEnabledChanged() = notifyListeners() } @@ -86,7 +97,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr FACE_UNLOCK_BYPASS_NEVER -> false else -> field } - return enabled && mKeyguardStateController.isFaceAuthEnabled + return enabled && mKeyguardStateController.isFaceAuthEnabled && + isPostureAllowedForFaceAuth() } private set(value) { field = value @@ -106,18 +118,31 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr lockscreenUserManager: NotificationLockscreenUserManager, keyguardStateController: KeyguardStateController, shadeExpansionStateManager: ShadeExpansionStateManager, + devicePostureController: DevicePostureController, dumpManager: DumpManager ) { this.mKeyguardStateController = keyguardStateController this.statusBarStateController = statusBarStateController + this.devicePostureController = devicePostureController bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override) + configFaceAuthSupportedPosture = + context.resources.getInteger(R.integer.config_face_auth_supported_posture) - hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE) + hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE) if (!hasFaceFeature) { return } + if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) { + devicePostureController.addCallback { posture -> + if (postureState != posture) { + postureState = posture + notifyListeners() + } + } + } + dumpManager.registerDumpable("KeyguardBypassController", this) statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onStateChanged(newState: Int) { @@ -203,6 +228,13 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr pendingUnlock = null } + fun isPostureAllowedForFaceAuth(): Boolean { + return when (configFaceAuthSupportedPosture) { + DEVICE_POSTURE_UNKNOWN -> true + else -> (postureState == configFaceAuthSupportedPosture) + } + } + override fun dump(pw: PrintWriter, args: Array) { pw.println("KeyguardBypassController:") if (pendingUnlock != null) { @@ -219,6 +251,7 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr pw.println(" launchingAffordance: $launchingAffordance") pw.println(" qSExpanded: $qsExpanded") pw.println(" hasFaceFeature: $hasFaceFeature") + pw.println(" postureState: $postureState") } /** Registers a listener for bypass state changes. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt new file mode 100644 index 0000000000000..3e90ed9811d02 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -0,0 +1,255 @@ +/* + * 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.statusbar.phone + +import android.content.pm.PackageManager +import android.test.suitebuilder.annotation.SmallTest +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.policy.DevicePostureController +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_FLIPPED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.tuner.TunerService +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardBypassControllerTest : SysuiTestCase() { + + private lateinit var keyguardBypassController: KeyguardBypassController + private lateinit var postureControllerCallback: DevicePostureController.Callback + @Mock private lateinit var tunerService: TunerService + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock private lateinit var devicePostureController: DevicePostureController + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var packageManager: PackageManager + @Captor + private val postureCallbackCaptor = + ArgumentCaptor.forClass(DevicePostureController.Callback::class.java) + @JvmField @Rule val mockito = MockitoJUnit.rule() + + @Before + fun setUp() { + context.setMockPackageManager(packageManager) + whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true) + whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true) + } + + @After + fun tearDown() { + reset(devicePostureController) + reset(keyguardStateController) + } + + private fun defaultConfigPostureClosed() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_CLOSED + ) + initKeyguardBypassController() + verify(devicePostureController).addCallback(postureCallbackCaptor.capture()) + postureControllerCallback = postureCallbackCaptor.value + } + + private fun defaultConfigPostureOpened() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_OPENED + ) + initKeyguardBypassController() + verify(devicePostureController).addCallback(postureCallbackCaptor.capture()) + postureControllerCallback = postureCallbackCaptor.value + } + + private fun defaultConfigPostureFlipped() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_FLIPPED + ) + initKeyguardBypassController() + verify(devicePostureController).addCallback(postureCallbackCaptor.capture()) + postureControllerCallback = postureCallbackCaptor.value + } + + private fun defaultConfigPostureUnknown() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_auth_supported_posture, + DEVICE_POSTURE_UNKNOWN + ) + initKeyguardBypassController() + verify(devicePostureController, never()).addCallback(postureCallbackCaptor.capture()) + } + + private fun initKeyguardBypassController() { + keyguardBypassController = + KeyguardBypassController( + context, + tunerService, + statusBarStateController, + lockscreenUserManager, + keyguardStateController, + shadeExpansionStateManager, + devicePostureController, + dumpManager + ) + } + + @Test + fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() { + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue() + } + + @Test + fun configDevicePostureOpen_matchState_isPostureAllowedForFaceAuth_returnTrue() { + defaultConfigPostureOpened() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue() + } + + @Test + fun configDevicePostureFlipped_matchState_isPostureAllowedForFaceAuth_returnTrue() { + defaultConfigPostureFlipped() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isTrue() + } + + @Test + fun configDevicePostureClosed_changeOpened_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureClosed_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureOpened_changeClosed_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureOpened() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureOpened_changeFlipped_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureOpened() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_FLIPPED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureFlipped_changeClosed_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureFlipped() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun configDevicePostureFlipped_changeOpened_isPostureAllowedForFaceAuth_returnFalse() { + defaultConfigPostureFlipped() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.isPostureAllowedForFaceAuth()).isFalse() + } + + @Test + fun defaultConfigPostureClosed_canOverrideByPassAlways_shouldReturnFalse() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 1 /* FACE_UNLOCK_BYPASS_ALWAYS */ + ) + + defaultConfigPostureClosed() + + postureControllerCallback.onPostureChanged(DEVICE_POSTURE_OPENED) + + assertThat(keyguardBypassController.bypassEnabled).isFalse() + } + + @Test + fun defaultConfigPostureUnknown_canNotOverrideByPassAlways_shouldReturnTrue() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 1 /* FACE_UNLOCK_BYPASS_ALWAYS */ + ) + + defaultConfigPostureUnknown() + + assertThat(keyguardBypassController.bypassEnabled).isTrue() + } + + @Test + fun defaultConfigPostureUnknown_canNotOverrideByPassNever_shouldReturnFalse() { + context.orCreateTestableResources.addOverride( + R.integer.config_face_unlock_bypass_override, + 2 /* FACE_UNLOCK_BYPASS_NEVER */ + ) + + defaultConfigPostureUnknown() + + assertThat(keyguardBypassController.bypassEnabled).isFalse() + } +}