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
This commit is contained in:
@@ -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<OnBypassStateChangedListener>()
|
||||
|
||||
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<out String>) {
|
||||
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. */
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user