diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 815ac6801b2ec..e42f051bf3bb9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -120,6 +120,7 @@ public class AuthContainerView extends LinearLayout private final Interpolator mLinearOutSlowIn; private final LockPatternUtils mLockPatternUtils; private final WakefulnessLifecycle mWakefulnessLifecycle; + private final AuthDialogPanelInteractionDetector mPanelInteractionDetector; private final InteractionJankMonitor mInteractionJankMonitor; // TODO: these should be migrated out once ready @@ -141,7 +142,6 @@ public class AuthContainerView extends LinearLayout private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; private final @Background DelayableExecutor mBackgroundExecutor; - private boolean mIsOrientationChanged = false; // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; @@ -235,6 +235,7 @@ public class AuthContainerView extends LinearLayout @Nullable List fpProps, @Nullable List faceProps, @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @@ -242,8 +243,9 @@ public class AuthContainerView extends LinearLayout @NonNull Provider credentialViewModelProvider) { mConfig.mSensorIds = sensorIds; return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle, - userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor, - credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor); + panelInteractionDetector, userManager, lockPatternUtils, jankMonitor, + biometricPromptInteractor, credentialViewModelProvider, + new Handler(Looper.getMainLooper()), bgExecutor); } } @@ -331,6 +333,7 @@ public class AuthContainerView extends LinearLayout @Nullable List fpProps, @Nullable List faceProps, @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @@ -346,6 +349,7 @@ public class AuthContainerView extends LinearLayout mHandler = mainHandler; mWindowManager = mContext.getSystemService(WindowManager.class); mWakefulnessLifecycle = wakefulnessLifecycle; + mPanelInteractionDetector = panelInteractionDetector; mTranslationY = getResources() .getDimension(R.dimen.biometric_dialog_animation_translation_offset); @@ -490,22 +494,6 @@ public class AuthContainerView extends LinearLayout @Override public void onOrientationChanged() { maybeUpdatePositionForUdfps(true /* invalidate */); - mIsOrientationChanged = true; - } - - @Override - public void onWindowFocusChanged(boolean hasWindowFocus) { - super.onWindowFocusChanged(hasWindowFocus); - if (!hasWindowFocus) { - //it's a workaround to avoid closing BP incorrectly - //BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true) - if (mIsOrientationChanged) { - mIsOrientationChanged = false; - return; - } - Log.v(TAG, "Lost window focus, dismissing the dialog"); - animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); - } } @Override @@ -513,6 +501,8 @@ public class AuthContainerView extends LinearLayout super.onAttachedToWindow(); mWakefulnessLifecycle.addObserver(this); + mPanelInteractionDetector.enable( + () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { mBiometricScrollView.addView(mBiometricView); @@ -666,11 +656,6 @@ public class AuthContainerView extends LinearLayout mBiometricView.restoreState(savedState); } - if (savedState != null) { - mIsOrientationChanged = savedState.getBoolean( - AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED); - } - wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle())); } @@ -689,6 +674,7 @@ public class AuthContainerView extends LinearLayout @Override public void dismissWithoutCallback(boolean animate) { + mPanelInteractionDetector.disable(); if (animate) { animateAway(false /* sendReason */, 0 /* reason */); } else { @@ -699,6 +685,7 @@ public class AuthContainerView extends LinearLayout @Override public void dismissFromSystemServer() { + mPanelInteractionDetector.disable(); animateAway(false /* sendReason */, 0 /* reason */); } @@ -761,8 +748,6 @@ public class AuthContainerView extends LinearLayout mBiometricView != null && mCredentialView == null); outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null); - outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged); - if (mBiometricView != null) { mBiometricView.onSaveState(outState); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index a0f3ecb0634bc..dad6ebe401849 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -164,6 +164,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull private final SparseBooleanArray mSfpsEnrolledForUser; @NonNull private final SensorPrivacyManager mSensorPrivacyManager; private final WakefulnessLifecycle mWakefulnessLifecycle; + private final AuthDialogPanelInteractionDetector mPanelInteractionDetector; private boolean mAllFingerprintAuthenticatorsRegistered; @NonNull private final UserManager mUserManager; @NonNull private final LockPatternUtils mLockPatternUtils; @@ -721,6 +722,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, Provider sidefpsControllerFactory, @NonNull DisplayManager displayManager, @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull UdfpsLogger udfpsLogger, @@ -767,6 +769,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, }); mWakefulnessLifecycle = wakefulnessLifecycle; + mPanelInteractionDetector = panelInteractionDetector; + mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; int[] faceAuthLocation = context.getResources().getIntArray( @@ -1149,6 +1153,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, requestId, multiSensorConfig, mWakefulnessLifecycle, + mPanelInteractionDetector, mUserManager, mLockPatternUtils); @@ -1239,6 +1244,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricMultiSensorMode int multiSensorConfig, @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils) { return new AuthContainerView.Builder(mContext) @@ -1253,8 +1259,9 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, .setMultiSensorConfig(multiSensorConfig) .setScaleFactorProvider(() -> getScaleFactor()) .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, - userManager, lockPatternUtils, mInteractionJankMonitor, - mBiometricPromptInteractor, mCredentialViewModelProvider); + panelInteractionDetector, userManager, lockPatternUtils, + mInteractionJankMonitor, mBiometricPromptInteractor, + mCredentialViewModelProvider); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index cd0fc37375945..51f39b3586595 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -48,8 +48,6 @@ public interface AuthDialog extends Dumpable { String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type"; String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props"; - String KEY_BIOMETRIC_ORIENTATION_CHANGED = "orientation_changed"; - int SIZE_UNKNOWN = 0; /** * Minimal UI, showing only biometric icon. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt new file mode 100644 index 0000000000000..64211b5b138e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -0,0 +1,53 @@ +package com.android.systemui.biometrics + +import android.annotation.AnyThread +import android.annotation.MainThread +import android.util.Log +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.shade.ShadeExpansionChangeEvent +import com.android.systemui.shade.ShadeExpansionStateManager +import java.util.concurrent.Executor +import javax.inject.Inject + +class AuthDialogPanelInteractionDetector +@Inject +constructor( + private val shadeExpansionStateManager: ShadeExpansionStateManager, + @Main private val mainExecutor: Executor, +) { + private var action: Action? = null + + @MainThread + fun enable(onPanelInteraction: Runnable) { + if (action == null) { + action = Action(onPanelInteraction) + shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) + } else { + Log.e(TAG, "Already enabled") + } + } + + @MainThread + fun disable() { + if (action != null) { + action = null + shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged) + } + } + + @AnyThread + private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) = + mainExecutor.execute { + action?.let { + if (event.tracking) { + Log.v(TAG, "Detected panel interaction, event: $event") + it.onPanelInteraction.run() + disable() + } + } + } +} + +private data class Action(val onPanelInteraction: Runnable) + +private const val TAG = "AuthDialogPanelInteractionDetector" diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 898f37048eec3..b4696e49e9aa5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -35,8 +35,6 @@ import android.view.WindowInsets import android.view.WindowManager import android.widget.ScrollView import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn -import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils import com.android.systemui.R @@ -81,6 +79,8 @@ class AuthContainerViewTest : SysuiTestCase() { @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock + lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector + @Mock lateinit var windowToken: IBinder @Mock lateinit var interactionJankMonitor: InteractionJankMonitor @@ -169,26 +169,6 @@ class AuthContainerViewTest : SysuiTestCase() { verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L) } - @Test - fun testDismissesOnFocusLoss() { - val container = initializeFingerprintContainer() - waitForIdleSync() - - val requestID = authContainer?.requestId ?: 0L - - verify(callback).onDialogAnimatedIn(requestID) - - container.onWindowFocusChanged(false) - waitForIdleSync() - - verify(callback).onDismissed( - eq(AuthDialogCallback.DISMISSED_USER_CANCELED), - eq(null), /* credentialAttestation */ - eq(requestID) - ) - assertThat(container.parent).isNull() - } - @Test fun testFocusLossAfterRotating() { val container = initializeFingerprintContainer() @@ -208,35 +188,6 @@ class AuthContainerViewTest : SysuiTestCase() { ) } - @Test - fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() { - val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - ) - waitForIdleSync() - - val requestID = authContainer?.requestId ?: 0L - - // Simulate keyboard was shown on the credential view - val windowInsetsController = container.windowInsetsController - spyOn(windowInsetsController) - spyOn(container.rootWindowInsets) - doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime()) - - container.onWindowFocusChanged(false) - waitForIdleSync() - - // Expect hiding IME request will be invoked when dismissing the view - verify(windowInsetsController)?.hide(WindowInsets.Type.ime()) - - verify(callback).onDismissed( - eq(AuthDialogCallback.DISMISSED_USER_CANCELED), - eq(null), /* credentialAttestation */ - eq(requestID) - ) - assertThat(container.parent).isNull() - } - @Test fun testActionAuthenticated_sendsDismissedAuthenticated() { val container = initializeFingerprintContainer() @@ -519,6 +470,7 @@ class AuthContainerViewTest : SysuiTestCase() { fingerprintProps, faceProps, wakefulnessLifecycle, + panelInteractionDetector, userManager, lockPatternUtils, interactionJankMonitor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 67b293f44cf4f..5afe49ea3cb32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -152,6 +152,8 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock + private AuthDialogPanelInteractionDetector mPanelInteractionDetector; + @Mock private UserManager mUserManager; @Mock private LockPatternUtils mLockPatternUtils; @@ -953,9 +955,10 @@ public class AuthControllerTest extends SysuiTestCase { super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, () -> mUdfpsController, () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle, - mUserManager, mLockPatternUtils, mUdfpsLogger, mLogContextInteractor, - () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel, - mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper); + mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger, + mLogContextInteractor, () -> mBiometricPromptCredentialInteractor, + () -> mCredentialViewModel, mInteractionJankMonitor, mHandler, + mBackgroundExecutor, mVibratorHelper); } @Override @@ -963,7 +966,9 @@ public class AuthControllerTest extends SysuiTestCase { boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricManager.BiometricMultiSensorMode int multiSensorConfig, - WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager, + WakefulnessLifecycle wakefulnessLifecycle, + AuthDialogPanelInteractionDetector panelInteractionDetector, + UserManager userManager, LockPatternUtils lockPatternUtils) { mLastBiometricPromptInfo = promptInfo;