Merge "Add face auth a11y action to bouncer view" into udc-d1-dev am: 2b0765ae5b

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24163069

Change-Id: I458aecd3f2276dc6adbabe978e495abd02997c42
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Treehugger Robot
2023-07-25 17:05:12 +00:00
committed by Automerger Merge Worker
10 changed files with 207 additions and 3 deletions

View File

@@ -2563,6 +2563,9 @@
<!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] --> <!-- Tooltip to show in management screen when there are multiple structures [CHAR_LIMIT=50] -->
<string name="controls_structure_tooltip">Swipe to see more</string> <string name="controls_structure_tooltip">Swipe to see more</string>
<!-- Accessibility action informing the user how they can retry face authentication [CHAR LIMIT=NONE] -->
<string name="retry_face">Retry face authentication</string>
<!-- Message to tell the user to wait while systemui attempts to load a set of <!-- Message to tell the user to wait while systemui attempts to load a set of
recommended controls [CHAR_LIMIT=60] --> recommended controls [CHAR_LIMIT=60] -->
<string name="controls_seeding_in_progress">Loading recommendations</string> <string name="controls_seeding_in_progress">Loading recommendations</string>

View File

@@ -20,6 +20,7 @@ import android.annotation.StringDef
import android.os.PowerManager import android.os.PowerManager
import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED
@@ -71,6 +72,7 @@ import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
NOTIFICATION_PANEL_CLICKED, NOTIFICATION_PANEL_CLICKED,
QS_EXPANDED, QS_EXPANDED,
PICK_UP_GESTURE_TRIGGERED, PICK_UP_GESTURE_TRIGGERED,
ACCESSIBILITY_ACTION,
) )
annotation class FaceAuthApiRequestReason { annotation class FaceAuthApiRequestReason {
companion object { companion object {
@@ -80,6 +82,7 @@ annotation class FaceAuthApiRequestReason {
const val QS_EXPANDED = "Face auth due to QS expansion." const val QS_EXPANDED = "Face auth due to QS expansion."
const val PICK_UP_GESTURE_TRIGGERED = const val PICK_UP_GESTURE_TRIGGERED =
"Face auth due to pickup gesture triggered when the device is awake and not from AOD." "Face auth due to pickup gesture triggered when the device is awake and not from AOD."
const val ACCESSIBILITY_ACTION = "Face auth due to an accessibility action."
} }
} }
@@ -217,7 +220,8 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
@UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED) @UiEvent(doc = STRONG_AUTH_ALLOWED_CHANGED)
FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED), FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
@UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED) @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED); FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED),
@UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION);
override fun getId(): Int = this.id override fun getId(): Int = this.id
@@ -233,6 +237,8 @@ private val apiRequestReasonToUiEvent =
FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED,
QS_EXPANDED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, QS_EXPANDED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED,
PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
PICK_UP_GESTURE_TRIGGERED to FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED,
ACCESSIBILITY_ACTION to FaceAuthUiEvent.FACE_AUTH_ACCESSIBILITY_ACTION,
) )
/** Converts the [reason] to the corresponding [FaceAuthUiEvent]. */ /** Converts the [reason] to the corresponding [FaceAuthUiEvent]. */

View File

@@ -68,6 +68,7 @@ import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken; import com.android.systemui.Gefingerpoken;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource; import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -385,9 +386,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
TelephonyManager telephonyManager, TelephonyManager telephonyManager,
ViewMediatorCallback viewMediatorCallback, ViewMediatorCallback viewMediatorCallback,
AudioManager audioManager, AudioManager audioManager,
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate
) { ) {
super(view); super(view);
view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
mLockPatternUtils = lockPatternUtils; mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor; mUpdateMonitor = keyguardUpdateMonitor;
mSecurityModel = keyguardSecurityModel; mSecurityModel = keyguardSecurityModel;

View File

@@ -3136,6 +3136,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return false; return false;
} }
if (isFaceAuthInteractorEnabled()) {
return mFaceAuthInteractor.canFaceAuthRun();
}
final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED; final boolean statusBarShadeLocked = mStatusBarState == StatusBarState.SHADE_LOCKED;
final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive final boolean awakeKeyguard = isKeyguardVisible() && mDeviceInteractive
&& !statusBarShadeLocked; && !statusBarShadeLocked;

View File

@@ -0,0 +1,62 @@
/*
* 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.biometrics
import android.content.res.Resources
import android.os.Bundle
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import com.android.keyguard.FaceAuthApiRequestReason
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import javax.inject.Inject
/**
* Accessibility delegate that will add a click accessibility action to a view when face auth can
* run. When the click a11y action is triggered, face auth will retry.
*/
@SysUISingleton
class FaceAuthAccessibilityDelegate
@Inject
constructor(
@Main private val resources: Resources,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val faceAuthInteractor: KeyguardFaceAuthInteractor,
) : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
if (keyguardUpdateMonitor.shouldListenForFace()) {
val clickActionToRetryFace =
AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
resources.getString(R.string.retry_face)
)
info.addAction(clickActionToRetryFace)
}
}
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
faceAuthInteractor.onAccessibilityAction()
true
} else super.performAccessibilityAction(host, action, args)
}
}

View File

@@ -60,6 +60,7 @@ interface KeyguardFaceAuthInteractor {
fun onNotificationPanelClicked() fun onNotificationPanelClicked()
fun onSwipeUpOnBouncer() fun onSwipeUpOnBouncer()
fun onPrimaryBouncerUserInput() fun onPrimaryBouncerUserInput()
fun onAccessibilityAction()
} }
/** /**

View File

@@ -60,4 +60,5 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt
override fun onSwipeUpOnBouncer() {} override fun onSwipeUpOnBouncer() {}
override fun onPrimaryBouncerUserInput() {} override fun onPrimaryBouncerUserInput() {}
override fun onAccessibilityAction() {}
} }

View File

@@ -133,6 +133,10 @@ constructor(
runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false) runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
} }
override fun onAccessibilityAction() {
runFaceAuth(FaceAuthUiEvent.FACE_AUTH_ACCESSIBILITY_ACTION, false)
}
override fun registerListener(listener: FaceAuthenticationListener) { override fun registerListener(listener: FaceAuthenticationListener) {
listeners.add(listener) listeners.add(listener)
} }

View File

@@ -60,6 +60,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource; import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -158,6 +159,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
private ViewMediatorCallback mViewMediatorCallback; private ViewMediatorCallback mViewMediatorCallback;
@Mock @Mock
private AudioManager mAudioManager; private AudioManager mAudioManager;
@Mock
private FaceAuthAccessibilityDelegate mFaceAuthAccessibilityDelegate;
@Captor @Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback; private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@@ -216,7 +219,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mUserSwitcherController, mFeatureFlags, mGlobalSettings, mUserSwitcherController, mFeatureFlags, mGlobalSettings,
mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate, mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
mTelephonyManager, mViewMediatorCallback, mAudioManager, mTelephonyManager, mViewMediatorCallback, mAudioManager,
mock(KeyguardFaceAuthInteractor.class)); mock(KeyguardFaceAuthInteractor.class),
mFaceAuthAccessibilityDelegate);
} }
@Test @Test
@@ -685,6 +689,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
verify(mView).setTranslationY(0f); verify(mView).setTranslationY(0f);
} }
@Test
public void setAccessibilityDelegate() {
verify(mView).setAccessibilityDelegate(eq(mFaceAuthAccessibilityDelegate));
}
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() { private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached(); mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture()); verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());

View File

@@ -0,0 +1,111 @@
/*
* 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.biometrics
import android.testing.TestableLooper
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.FaceAuthApiRequestReason
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@SmallTest
@TestableLooper.RunWithLooper
class FaceAuthAccessibilityDelegateTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var hostView: View
@Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
private lateinit var underTest: FaceAuthAccessibilityDelegate
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
FaceAuthAccessibilityDelegate(
context.resources,
keyguardUpdateMonitor,
faceAuthInteractor,
)
}
@Test
fun shouldListenForFaceTrue_onInitializeAccessibilityNodeInfo_clickActionAdded() {
whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
// WHEN node is initialized
val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
// THEN a11y action is added
val argumentCaptor = argumentCaptor<AccessibilityNodeInfo.AccessibilityAction>()
verify(mockedNodeInfo).addAction(argumentCaptor.capture())
// AND the a11y action is a click action
assertEquals(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
argumentCaptor.value.id
)
}
@Test
fun shouldListenForFaceFalse_onInitializeAccessibilityNodeInfo_clickActionNotAdded() {
whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
// WHEN node is initialized
val mockedNodeInfo = mock(AccessibilityNodeInfo::class.java)
underTest.onInitializeAccessibilityNodeInfo(hostView, mockedNodeInfo)
// THEN a11y action is NOT added
verify(mockedNodeInfo, never())
.addAction(any(AccessibilityNodeInfo.AccessibilityAction::class.java))
}
@Test
fun performAccessibilityAction_actionClick_retriesFaceAuth() {
whenever(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
// WHEN click action is performed
underTest.performAccessibilityAction(
hostView,
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
null
)
// THEN retry face auth
verify(keyguardUpdateMonitor)
.requestFaceAuth(eq(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION))
verify(faceAuthInteractor).onAccessibilityAction()
}
}