From 79dd7bb47b182a0ab64c9ee27bf79cb5621f0e98 Mon Sep 17 00:00:00 2001 From: lbill Date: Mon, 9 Jan 2023 18:32:53 +0000 Subject: [PATCH] 4-1/ KeyguardBypassController listen for device posture change The bypassEnabled only override by config_face_unlock_bypass_override Hence the device config_face_auth_supported_posture will 'always' or 'never' bypass keyguard on each posture, the case is device config only allow 'DEVICE_POSTURE_CLOSED' for face auth, but when device in DEVICE_POSTURE_OPENED state, KeyguardBypassController thought device should always bypass keyguard after face auth. However, face auth in KeyguardUpdateMonitor do not register or listen for the face service, then systemui or keyguard could trap into unexpected state. The symptons would be: 1. Do not see notification shade in DEVICE_POSTURE_OPENED 2. Observe QS tile on largescreen portrait mode Test: atest SystemUITests Test: KeyguardBypassControllerTest Test: manual enroll face, check device in un-supported face auth posture state, showing notifications Test: Manual check QS panel collapsed and in expected postition Test: Check unlock by pattern never stuck Bug: 263248370 Bug: 264375814 Bug: 264799288 Bug: 264342055 Change-Id: I6096dfcf5d863e31bb7a37f0609d2f5b790c68d9 --- .../phone/KeyguardBypassController.kt | 39 ++- .../phone/KeyguardBypassControllerTest.kt | 255 ++++++++++++++++++ 2 files changed, 291 insertions(+), 3 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt 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() + } +}