Merge "Include all flags in our flag list." into tm-qpr-dev
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index babac7b..70a464e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -224,6 +224,7 @@
"androidx.test.rules",
"androidx.test.uiautomator",
"mockito-target-extended-minus-junit4",
+ "androidx.test.ext.junit",
"testables",
"truth-prebuilt",
"monet",
diff --git a/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml b/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml
new file mode 100644
index 0000000..ae0f4b2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/user_switcher_fullscreen_button_bg.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:insetTop="@dimen/dialog_button_vertical_inset"
+ android:insetBottom="@dimen/dialog_button_vertical_inset">
+ <ripple android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="20dp"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="20dp"/>
+ <solid android:color="@android:color/transparent"/>
+ <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"
+ />
+ <padding android:left="@dimen/dialog_button_horizontal_padding"
+ android:top="@dimen/dialog_button_vertical_padding"
+ android:right="@dimen/dialog_button_horizontal_padding"
+ android:bottom="@dimen/dialog_button_vertical_padding"/>
+ </shape>
+ </item>
+ </ripple>
+</inset>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index 78884ff..fa9d739 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -80,7 +80,7 @@
<TextView
android:id="@+id/add"
- style="@style/Widget.Dialog.Button.BorderButton"
+ android:background="@drawable/user_switcher_fullscreen_button_bg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a7f8cb6..c88da18 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -403,6 +403,8 @@
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
+ <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=25] -->
+ <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 91fd6a6..ebc41861 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -52,6 +52,11 @@
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.lifecycle_lifecycle-viewmodel-ktx",
+ "androidx.recyclerview_recyclerview",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
"gson-prebuilt-jar",
"dagger2",
"jsr330",
@@ -64,6 +69,7 @@
},
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
+ kotlincflags: ["-Xjvm-default=enable"],
}
java_library {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index fbb114c..7a49926 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -58,7 +58,8 @@
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
+import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
@@ -100,7 +101,7 @@
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
private final SessionTracker mSessionTracker;
- private final Optional<SidefpsController> mSidefpsController;
+ private final Optional<SideFpsController> mSideFpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -290,7 +291,7 @@
FeatureFlags featureFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
- Optional<SidefpsController> sidefpsController,
+ Optional<SideFpsController> sideFpsController,
FalsingA11yDelegate falsingA11yDelegate) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -311,7 +312,7 @@
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
- mSidefpsController = sidefpsController;
+ mSideFpsController = sideFpsController;
mFalsingA11yDelegate = falsingA11yDelegate;
}
@@ -351,7 +352,7 @@
}
private void updateSideFpsVisibility() {
- if (!mSidefpsController.isPresent()) {
+ if (!mSideFpsController.isPresent()) {
return;
}
final boolean sfpsEnabled = getResources().getBoolean(
@@ -369,9 +370,9 @@
+ "needsStrongAuth=" + needsStrongAuth);
}
if (toShow) {
- mSidefpsController.get().show();
+ mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
} else {
- mSidefpsController.get().hide();
+ mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
}
}
@@ -745,7 +746,7 @@
private final FeatureFlags mFeatureFlags;
private final UserSwitcherController mUserSwitcherController;
private final SessionTracker mSessionTracker;
- private final Optional<SidefpsController> mSidefpsController;
+ private final Optional<SideFpsController> mSidefpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
@Inject
@@ -766,7 +767,7 @@
FeatureFlags featureFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
- Optional<SidefpsController> sidefpsController,
+ Optional<SideFpsController> sidefpsController,
FalsingA11yDelegate falsingA11yDelegate) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 558869c..1bb83f4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -798,7 +798,7 @@
mFingerprintCancelSignal = null;
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
- mLogger.d("onFingerprintAuthenticated");
+ mLogger.logFingerprintSuccess(userId, isStrongBiometric);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2716,7 +2716,7 @@
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
// TODO: always disallow when fp is already locked out?
- final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
@@ -2745,7 +2745,6 @@
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
- final boolean fpOrFaceIsLockedOut = isFaceLockedOut() || fpLockedout;
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2763,7 +2762,10 @@
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& !faceAuthenticated
&& !mGoingToSleep
- && !fpOrFaceIsLockedOut;
+ // We only care about fp locked out state and not face because we still trigger
+ // face auth even when face is locked out to show the user a message that face
+ // unlock was supposed to run but didn't
+ && !fpLockedOut;
// Aggregate relevant fields for debug logging.
maybeLogListenerModelData(
@@ -2778,7 +2780,7 @@
faceAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
- fpLockedout,
+ fpLockedOut,
mGoingToSleep,
awakeKeyguard,
mKeyguardGoingAway,
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index 49e9783..ef067b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -16,7 +16,7 @@
package com.android.keyguard.dagger;
-import static com.android.systemui.biometrics.SidefpsControllerKt.hasSideFpsSensor;
+import static com.android.systemui.biometrics.SideFpsControllerKt.hasSideFpsSensor;
import android.annotation.Nullable;
import android.hardware.fingerprint.FingerprintManager;
@@ -27,7 +27,7 @@
import com.android.keyguard.KeyguardSecurityContainer;
import com.android.keyguard.KeyguardSecurityViewFlipper;
import com.android.systemui.R;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -70,12 +70,12 @@
return containerView.findViewById(R.id.view_flipper);
}
- /** Provides {@link SidefpsController} if the device has the side fingerprint sensor. */
+ /** Provides {@link SideFpsController} if the device has the side fingerprint sensor. */
@Provides
@KeyguardBouncerScope
- static Optional<SidefpsController> providesOptionalSidefpsController(
+ static Optional<SideFpsController> providesOptionalSidefpsController(
@Nullable FingerprintManager fingerprintManager,
- Provider<SidefpsController> sidefpsControllerProvider) {
+ Provider<SideFpsController> sidefpsControllerProvider) {
if (!hasSideFpsSensor(fingerprintManager)) {
return Optional.empty();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 3308f55..6276142d 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -153,6 +153,13 @@
{ "fingerprintRunningState: $int1" })
}
+ fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = userId
+ bool1 = isStrongBiometric
+ }, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
+ }
+
fun logInvalidSubId(subId: Int) {
logBuffer.log(TAG, INFO,
{ int1 = subId },
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ff18eee..eedf423 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -119,7 +119,7 @@
@Nullable private final FingerprintManager mFingerprintManager;
@Nullable private final FaceManager mFaceManager;
private final Provider<UdfpsController> mUdfpsControllerFactory;
- private final Provider<SidefpsController> mSidefpsControllerFactory;
+ private final Provider<SideFpsController> mSidefpsControllerFactory;
private final Display mDisplay;
private float mScaleFactor = 1f;
@@ -141,7 +141,7 @@
@NonNull private final DisplayManager mDisplayManager;
@Nullable private UdfpsController mUdfpsController;
@Nullable private IUdfpsHbmListener mUdfpsHbmListener;
- @Nullable private SidefpsController mSidefpsController;
+ @Nullable private SideFpsController mSideFpsController;
@Nullable private IBiometricContextListener mBiometricContextListener;
@VisibleForTesting IBiometricSysuiReceiver mReceiver;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@@ -316,7 +316,7 @@
mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
if (mSidefpsProps != null) {
- mSidefpsController = mSidefpsControllerFactory.get();
+ mSideFpsController = mSidefpsControllerFactory.get();
}
updateSensorLocations();
@@ -677,7 +677,7 @@
@Nullable FingerprintManager fingerprintManager,
@Nullable FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
- Provider<SidefpsController> sidefpsControllerFactory,
+ Provider<SideFpsController> sidefpsControllerFactory,
@NonNull DisplayManager displayManager,
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@NonNull UserManager userManager,
@@ -777,13 +777,16 @@
private void updateUdfpsLocation() {
if (mUdfpsController != null) {
final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+
final Rect previousUdfpsBounds = mUdfpsBounds;
mUdfpsBounds = udfpsProp.getLocation().getRect();
mUdfpsBounds.scale(mScaleFactor);
- mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
- new UdfpsOverlayParams(mUdfpsBounds, mCachedDisplayInfo.getNaturalWidth(),
- mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,
- mCachedDisplayInfo.rotation));
+
+ final UdfpsOverlayParams overlayParams = new UdfpsOverlayParams(mUdfpsBounds,
+ mCachedDisplayInfo.getNaturalWidth(), mCachedDisplayInfo.getNaturalHeight(),
+ mScaleFactor, mCachedDisplayInfo.rotation);
+
+ mUdfpsController.updateOverlayParams(udfpsProp, overlayParams);
if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
for (Callback cb : mCallbacks) {
cb.onUdfpsLocationChanged();
@@ -1054,7 +1057,7 @@
* Whether the passed userId has enrolled SFPS.
*/
public boolean isSfpsEnrolled(int userId) {
- if (mSidefpsController == null) {
+ if (mSideFpsController == null) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
new file mode 100644
index 0000000..1c3dd45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2021 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.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.app.ActivityTaskManager
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManager
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.util.Log
+import android.util.RotationUtils
+import android.view.Display
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.accessibility.AccessibilityEvent
+import androidx.annotation.RawRes
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieProperty
+import com.airbnb.lottie.model.KeyPath
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+private const val TAG = "SideFpsController"
+
+/**
+ * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
+ */
+@SysUISingleton
+class SideFpsController
+@Inject
+constructor(
+ private val context: Context,
+ private val layoutInflater: LayoutInflater,
+ fingerprintManager: FingerprintManager?,
+ private val windowManager: WindowManager,
+ private val activityTaskManager: ActivityTaskManager,
+ overviewProxyService: OverviewProxyService,
+ displayManager: DisplayManager,
+ @Main private val mainExecutor: DelayableExecutor,
+ @Main private val handler: Handler,
+ dumpManager: DumpManager
+) : Dumpable {
+ val requests: HashSet<SideFpsUiRequestSource> = HashSet()
+
+ @VisibleForTesting
+ val sensorProps: FingerprintSensorPropertiesInternal =
+ fingerprintManager?.sideFpsSensorProperties
+ ?: throw IllegalStateException("no side fingerprint sensor")
+
+ @VisibleForTesting
+ val orientationListener =
+ BiometricDisplayListener(
+ context,
+ displayManager,
+ handler,
+ BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
+ ) { onOrientationChanged() }
+
+ @VisibleForTesting
+ val overviewProxyListener =
+ object : OverviewProxyService.OverviewProxyListener {
+ override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
+ overlayView?.let { view ->
+ handler.postDelayed({ updateOverlayVisibility(view) }, 500)
+ }
+ }
+ }
+
+ private val animationDuration =
+ context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
+
+ private var overlayHideAnimator: ViewPropertyAnimator? = null
+
+ private var overlayView: View? = null
+ set(value) {
+ field?.let { oldView ->
+ windowManager.removeView(oldView)
+ orientationListener.disable()
+ }
+ overlayHideAnimator?.cancel()
+ overlayHideAnimator = null
+
+ field = value
+ field?.let { newView ->
+ windowManager.addView(newView, overlayViewParams)
+ updateOverlayVisibility(newView)
+ orientationListener.enable()
+ }
+ }
+ @VisibleForTesting
+ internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+
+ private val overlayViewParams =
+ WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
+ Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+ PixelFormat.TRANSLUCENT
+ )
+ .apply {
+ title = TAG
+ fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+ gravity = Gravity.TOP or Gravity.LEFT
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
+ }
+
+ init {
+ fingerprintManager?.setSidefpsController(
+ object : ISidefpsController.Stub() {
+ override fun show(
+ sensorId: Int,
+ @BiometricOverlayConstants.ShowReason reason: Int
+ ) =
+ if (reason.isReasonToAutoShow(activityTaskManager)) {
+ show(SideFpsUiRequestSource.AUTO_SHOW)
+ } else {
+ hide(SideFpsUiRequestSource.AUTO_SHOW)
+ }
+
+ override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
+ }
+ )
+ overviewProxyService.addCallback(overviewProxyListener)
+ dumpManager.registerDumpable(this)
+ }
+
+ /** Shows the side fps overlay if not already shown. */
+ fun show(request: SideFpsUiRequestSource) {
+ requests.add(request)
+ mainExecutor.execute {
+ if (overlayView == null) {
+ createOverlayForDisplay()
+ } else {
+ Log.v(TAG, "overlay already shown")
+ }
+ }
+ }
+
+ /** Hides the fps overlay if shown. */
+ fun hide(request: SideFpsUiRequestSource) {
+ requests.remove(request)
+ mainExecutor.execute {
+ if (requests.isEmpty()) {
+ overlayView = null
+ }
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("requests:")
+ for (requestSource in requests) {
+ pw.println(" $requestSource.name")
+ }
+ }
+
+ private fun onOrientationChanged() {
+ if (overlayView != null) {
+ createOverlayForDisplay()
+ }
+ }
+
+ private fun createOverlayForDisplay() {
+ val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+ overlayView = view
+ val display = context.display!!
+ val offsets =
+ sensorProps.getLocation(display.uniqueId).let { location ->
+ if (location == null) {
+ Log.w(TAG, "No location specified for display: ${display.uniqueId}")
+ }
+ location ?: sensorProps.location
+ }
+ overlayOffsets = offsets
+
+ val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+ view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
+ lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
+ lottie.addLottieOnCompositionLoadedListener {
+ // Check that view is not stale, and that overlayView has not been hidden/removed
+ if (overlayView != null && overlayView == view) {
+ updateOverlayParams(display, it.bounds)
+ }
+ }
+ lottie.addOverlayDynamicColor(context)
+
+ /**
+ * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
+ * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
+ * in focus
+ */
+ view.setAccessibilityDelegate(
+ object : AccessibilityDelegate() {
+ override fun dispatchPopulateAccessibilityEvent(
+ host: View,
+ event: AccessibilityEvent
+ ): Boolean {
+ return if (
+ event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ ) {
+ true
+ } else {
+ super.dispatchPopulateAccessibilityEvent(host, event)
+ }
+ }
+ }
+ )
+ }
+
+ @VisibleForTesting
+ internal fun updateOverlayParams(display: Display, bounds: Rect) {
+ val isNaturalOrientation = display.isNaturalOrientation()
+ val size = windowManager.maximumWindowMetrics.bounds
+ val displayWidth = if (isNaturalOrientation) size.width() else size.height()
+ val displayHeight = if (isNaturalOrientation) size.height() else size.width()
+ val boundsWidth = if (isNaturalOrientation) bounds.width() else bounds.height()
+ val boundsHeight = if (isNaturalOrientation) bounds.height() else bounds.width()
+ val sensorBounds =
+ if (overlayOffsets.isYAligned()) {
+ Rect(
+ displayWidth - boundsWidth,
+ overlayOffsets.sensorLocationY,
+ displayWidth,
+ overlayOffsets.sensorLocationY + boundsHeight
+ )
+ } else {
+ Rect(
+ overlayOffsets.sensorLocationX,
+ 0,
+ overlayOffsets.sensorLocationX + boundsWidth,
+ boundsHeight
+ )
+ }
+
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ Rect(0, 0, displayWidth, displayHeight),
+ display.rotation
+ )
+
+ overlayViewParams.x = sensorBounds.left
+ overlayViewParams.y = sensorBounds.top
+ windowManager.updateViewLayout(overlayView, overlayViewParams)
+ }
+
+ private fun updateOverlayVisibility(view: View) {
+ if (view != overlayView) {
+ return
+ }
+ // hide after a few seconds if the sensor is oriented down and there are
+ // large overlapping system bars
+ val rotation = context.display?.rotation
+ if (
+ windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
+ ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
+ (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))
+ ) {
+ overlayHideAnimator =
+ view
+ .animate()
+ .alpha(0f)
+ .setStartDelay(3_000)
+ .setDuration(animationDuration)
+ .setListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ view.visibility = View.GONE
+ overlayHideAnimator = null
+ }
+ }
+ )
+ } else {
+ overlayHideAnimator?.cancel()
+ overlayHideAnimator = null
+ view.alpha = 1f
+ view.visibility = View.VISIBLE
+ }
+ }
+}
+
+private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
+ get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
+
+/** Returns [True] when the device has a side fingerprint sensor. */
+fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
+
+@BiometricOverlayConstants.ShowReason
+private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Boolean =
+ when (this) {
+ REASON_AUTH_KEYGUARD -> false
+ REASON_AUTH_SETTINGS ->
+ when (activityTaskManager.topClass()) {
+ // TODO(b/186176653): exclude fingerprint overlays from this list view
+ "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
+ else -> true
+ }
+ else -> true
+ }
+
+private fun ActivityTaskManager.topClass(): String =
+ getTasks(1).firstOrNull()?.topActivity?.className ?: ""
+
+@RawRes
+private fun Display.asSideFpsAnimation(yAligned: Boolean): Int =
+ when (rotation) {
+ Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
+ else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
+ }
+
+private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float =
+ when (rotation) {
+ Surface.ROTATION_90 -> if (yAligned) 0f else 180f
+ Surface.ROTATION_180 -> 180f
+ Surface.ROTATION_270 -> if (yAligned) 180f else 0f
+ else -> 0f
+ }
+
+private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
+
+private fun Display.isNaturalOrientation(): Boolean =
+ rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
+
+private fun WindowInsets.hasBigNavigationBar(): Boolean =
+ getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
+
+private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
+ fun update() {
+ val c = context.getColor(R.color.biometric_dialog_accent)
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
+ }
+ }
+ }
+
+ if (composition != null) {
+ update()
+ } else {
+ addLottieOnCompositionLoadedListener { update() }
+ }
+}
+
+/**
+ * The source of a request to show the side fps visual indicator. This is distinct from
+ * [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is
+ * requested.
+ */
+enum class SideFpsUiRequestSource {
+ /** see [isReasonToAutoShow] */
+ AUTO_SHOW,
+ /** Pin, pattern or password bouncer */
+ PRIMARY_BOUNCER,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
deleted file mode 100644
index d03106b..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright (C) 2021 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.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.app.ActivityTaskManager
-import android.content.Context
-import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffColorFilter
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.display.DisplayManager
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.ISidefpsController
-import android.os.Handler
-import android.util.Log
-import android.util.RotationUtils
-import android.view.Display
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.Surface
-import android.view.View
-import android.view.View.AccessibilityDelegate
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.accessibility.AccessibilityEvent
-import androidx.annotation.RawRes
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieProperty
-import com.airbnb.lottie.model.KeyPath
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.DelayableExecutor
-import javax.inject.Inject
-
-private const val TAG = "SidefpsController"
-
-/**
- * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
- */
-@SysUISingleton
-class SidefpsController @Inject constructor(
- private val context: Context,
- private val layoutInflater: LayoutInflater,
- fingerprintManager: FingerprintManager?,
- private val windowManager: WindowManager,
- private val activityTaskManager: ActivityTaskManager,
- overviewProxyService: OverviewProxyService,
- displayManager: DisplayManager,
- @Main private val mainExecutor: DelayableExecutor,
- @Main private val handler: Handler
-) {
- @VisibleForTesting
- val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
- ?.sideFpsSensorProperties
- ?: throw IllegalStateException("no side fingerprint sensor")
-
- @VisibleForTesting
- val orientationListener = BiometricDisplayListener(
- context,
- displayManager,
- handler,
- BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
- ) { onOrientationChanged() }
-
- @VisibleForTesting
- val overviewProxyListener = object : OverviewProxyService.OverviewProxyListener {
- override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
- overlayView?.let { view ->
- handler.postDelayed({ updateOverlayVisibility(view) }, 500)
- }
- }
- }
-
- private val animationDuration =
- context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()
-
- private var overlayHideAnimator: ViewPropertyAnimator? = null
-
- private var overlayView: View? = null
- set(value) {
- field?.let { oldView ->
- windowManager.removeView(oldView)
- orientationListener.disable()
- }
- overlayHideAnimator?.cancel()
- overlayHideAnimator = null
-
- field = value
- field?.let { newView ->
- windowManager.addView(newView, overlayViewParams)
- updateOverlayVisibility(newView)
- orientationListener.enable()
- }
- }
- @VisibleForTesting
- internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
-
- private val overlayViewParams = WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
- Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
- PixelFormat.TRANSLUCENT
- ).apply {
- title = TAG
- fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
- gravity = Gravity.TOP or Gravity.LEFT
- layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
- }
-
- init {
- fingerprintManager?.setSidefpsController(
- object : ISidefpsController.Stub() {
- override fun show(
- sensorId: Int,
- @BiometricOverlayConstants.ShowReason reason: Int
- ) = if (reason.isReasonToShow(activityTaskManager)) show() else hide()
-
- override fun hide(sensorId: Int) = hide()
- })
- overviewProxyService.addCallback(overviewProxyListener)
- }
-
- /** Shows the side fps overlay if not already shown. */
- fun show() {
- mainExecutor.execute {
- if (overlayView == null) {
- createOverlayForDisplay()
- } else {
- Log.v(TAG, "overlay already shown")
- }
- }
- }
-
- /** Hides the fps overlay if shown. */
- fun hide() {
- mainExecutor.execute { overlayView = null }
- }
-
- private fun onOrientationChanged() {
- if (overlayView != null) {
- createOverlayForDisplay()
- }
- }
-
- private fun createOverlayForDisplay() {
- val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
- overlayView = view
- val display = context.display!!
- val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
- if (location == null) {
- Log.w(TAG, "No location specified for display: ${display.uniqueId}")
- }
- location ?: sensorProps.location
- }
- overlayOffsets = offsets
-
- val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
- view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())
- lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
- lottie.addLottieOnCompositionLoadedListener {
- // Check that view is not stale, and that overlayView has not been hidden/removed
- if (overlayView != null && overlayView == view) {
- updateOverlayParams(display, it.bounds)
- }
- }
- lottie.addOverlayDynamicColor(context)
-
- /**
- * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
- * speaking @string/accessibility_fingerprint_label twice when sensor location indicator
- * is in focus
- */
- view.setAccessibilityDelegate(object : AccessibilityDelegate() {
- override fun dispatchPopulateAccessibilityEvent(
- host: View,
- event: AccessibilityEvent
- ): Boolean {
- return if (event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- true
- } else {
- super.dispatchPopulateAccessibilityEvent(host, event)
- }
- }
- })
- }
-
- @VisibleForTesting
- internal fun updateOverlayParams(display: Display, bounds: Rect) {
- val isNaturalOrientation = display.isNaturalOrientation()
- val size = windowManager.maximumWindowMetrics.bounds
- val displayWidth = if (isNaturalOrientation) size.width() else size.height()
- val displayHeight = if (isNaturalOrientation) size.height() else size.width()
- val boundsWidth = if (isNaturalOrientation) bounds.width() else bounds.height()
- val boundsHeight = if (isNaturalOrientation) bounds.height() else bounds.width()
- val sensorBounds = if (overlayOffsets.isYAligned()) {
- Rect(
- displayWidth - boundsWidth,
- overlayOffsets.sensorLocationY,
- displayWidth,
- overlayOffsets.sensorLocationY + boundsHeight
- )
- } else {
- Rect(
- overlayOffsets.sensorLocationX,
- 0,
- overlayOffsets.sensorLocationX + boundsWidth,
- boundsHeight
- )
- }
-
- RotationUtils.rotateBounds(
- sensorBounds,
- Rect(0, 0, displayWidth, displayHeight),
- display.rotation
- )
-
- overlayViewParams.x = sensorBounds.left
- overlayViewParams.y = sensorBounds.top
- windowManager.updateViewLayout(overlayView, overlayViewParams)
- }
-
- private fun updateOverlayVisibility(view: View) {
- if (view != overlayView) {
- return
- }
- // hide after a few seconds if the sensor is oriented down and there are
- // large overlapping system bars
- val rotation = context.display?.rotation
- if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
- ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
- (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) {
- overlayHideAnimator = view.animate()
- .alpha(0f)
- .setStartDelay(3_000)
- .setDuration(animationDuration)
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- view.visibility = View.GONE
- overlayHideAnimator = null
- }
- })
- } else {
- overlayHideAnimator?.cancel()
- overlayHideAnimator = null
- view.alpha = 1f
- view.visibility = View.VISIBLE
- }
- }
-}
-
-private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
- get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
-
-/** Returns [True] when the device has a side fingerprint sensor. */
-fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
-
-@BiometricOverlayConstants.ShowReason
-private fun Int.isReasonToShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) {
- REASON_AUTH_KEYGUARD -> false
- REASON_AUTH_SETTINGS -> when (activityTaskManager.topClass()) {
- // TODO(b/186176653): exclude fingerprint overlays from this list view
- "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
- else -> true
- }
- else -> true
-}
-
-private fun ActivityTaskManager.topClass(): String =
- getTasks(1).firstOrNull()?.topActivity?.className ?: ""
-
-@RawRes
-private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) {
- Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
- else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
-}
-
-private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) {
- Surface.ROTATION_90 -> if (yAligned) 0f else 180f
- Surface.ROTATION_180 -> 180f
- Surface.ROTATION_270 -> if (yAligned) 180f else 0f
- else -> 0f
-}
-
-private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0
-
-private fun Display.isNaturalOrientation(): Boolean =
- rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
-
-private fun WindowInsets.hasBigNavigationBar(): Boolean =
- getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
-
-private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
- fun update() {
- val c = context.getColor(R.color.biometric_dialog_accent)
- for (key in listOf(".blue600", ".blue400")) {
- addValueCallback(
- KeyPath(key, "**"),
- LottieProperty.COLOR_FILTER
- ) { PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP) }
- }
- }
-
- if (composition != null) {
- update()
- } else {
- addLottieOnCompositionLoadedListener { update() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 65fcd76..71711a2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -23,16 +23,17 @@
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.Handler;
@@ -50,10 +51,14 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
import com.android.systemui.dagger.SysUISingleton;
@@ -77,6 +82,8 @@
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.time.SystemClock;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -99,7 +106,7 @@
*/
@SuppressWarnings("deprecation")
@SysUISingleton
-public class UdfpsController implements DozeReceiver {
+public class UdfpsController implements DozeReceiver, Dumpable {
private static final String TAG = "UdfpsController";
private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
@@ -137,7 +144,7 @@
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
- @VisibleForTesting int mSensorId;
+ @VisibleForTesting @NonNull FingerprintSensorPropertiesInternal mSensorProps;
@VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -202,6 +209,11 @@
}
};
+ @Override
+ public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mSensorProps=(" + mSensorProps + ")");
+ }
+
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@@ -249,7 +261,7 @@
}
mAcquiredReceived = true;
final UdfpsView view = mOverlay.getOverlayView();
- if (view != null) {
+ if (view != null && isOptical()) {
unconfigureDisplay(view);
}
if (acquiredGood) {
@@ -295,12 +307,13 @@
/**
* Updates the overlay parameters and reconstructs or redraws the overlay, if necessary.
*
- * @param sensorId sensor for which the overlay is getting updated.
+ * @param sensorProps sensor for which the overlay is getting updated.
* @param overlayParams See {@link UdfpsOverlayParams}.
*/
- public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) {
- if (sensorId != mSensorId) {
- mSensorId = sensorId;
+ public void updateOverlayParams(@NonNull FingerprintSensorPropertiesInternal sensorProps,
+ @NonNull UdfpsOverlayParams overlayParams) {
+ if (mSensorProps.sensorId != sensorProps.sensorId) {
+ mSensorProps = sensorProps;
Log.w(TAG, "updateUdfpsParams | sensorId has changed");
}
@@ -324,7 +337,7 @@
mAuthControllerUpdateUdfpsLocation = r;
}
- public void setUdfpsDisplayMode(UdfpsDisplayMode udfpsDisplayMode) {
+ public void setUdfpsDisplayMode(@Nullable UdfpsDisplayMode udfpsDisplayMode) {
mUdfpsDisplayMode = udfpsDisplayMode;
}
@@ -428,7 +441,6 @@
}
final UdfpsView udfpsView = mOverlay.getOverlayView();
- final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
boolean handled = false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_OUTSIDE:
@@ -512,15 +524,14 @@
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
- if (!isDisplayConfigured && !mAcquiredReceived
- && !exceedsVelocityThreshold) {
+ if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) {
final float scale = mOverlayParams.getScaleFactor();
float scaledMinor = minor / scale;
float scaledMajor = major / scale;
-
onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor,
scaledMajor);
+
Log.v(TAG, "onTouch | finger down: " + touchInfo);
mTouchLogTime = mSystemClock.elapsedRealtime();
handled = true;
@@ -614,7 +625,7 @@
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
- @BiometricsBackground Executor biometricsExecutor,
+ @NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull BouncerInteractor bouncerInteractor) {
mContext = context;
mExecution = execution;
@@ -644,10 +655,20 @@
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+ mSensorProps = new FingerprintSensorPropertiesInternal(
+ -1 /* sensorId */,
+ SensorProperties.STRENGTH_CONVENIENCE,
+ 0 /* maxEnrollmentsPerUser */,
+ new ArrayList<>() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+
mBiometricExecutor = biometricsExecutor;
mFeatureFlags = featureFlags;
mBouncerInteractor = bouncerInteractor;
+ mDumpManager.registerDumpable(TAG, this);
+
mOrientationListener = new BiometricDisplayListener(
context,
displayManager,
@@ -843,6 +864,10 @@
mIsAodInterruptActive = false;
}
+ private boolean isOptical() {
+ return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+ }
+
public boolean isFingerDown() {
return mOnFingerDown;
}
@@ -859,7 +884,9 @@
+ " current: " + mOverlay.getRequestId());
return;
}
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+ if (isOptical()) {
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+ }
// Refresh screen timeout and boost process priority if possible.
mPowerManager.userActivity(mSystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
@@ -882,11 +909,11 @@
}
});
} else {
- mFingerprintManager.onPointerDown(requestId, mSensorId, x, y, minor, major);
+ mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major);
}
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
final UdfpsView view = mOverlay.getOverlayView();
- if (view != null) {
+ if (view != null && isOptical()) {
view.configureDisplay(() -> {
if (mAlternateTouchProvider != null) {
mBiometricExecutor.execute(() -> {
@@ -894,7 +921,7 @@
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
});
} else {
- mFingerprintManager.onUiReady(requestId, mSensorId);
+ mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
}
});
@@ -920,15 +947,16 @@
}
});
} else {
- mFingerprintManager.onPointerUp(requestId, mSensorId);
+ mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
}
for (Callback cb : mCallbacks) {
cb.onFingerUp();
}
}
mOnFingerDown = false;
- unconfigureDisplay(view);
-
+ if (isOptical()) {
+ unconfigureDisplay(view);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 662d059..8bddffc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -45,6 +45,7 @@
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
import javax.inject.Inject
/**
@@ -68,6 +69,7 @@
private val mediaTttFlags: MediaTttFlags,
private val uiEventLogger: MediaTttReceiverUiEventLogger,
private val viewUtil: ViewUtil,
+ wakeLockBuilder: WakeLock.Builder,
) : TemporaryViewDisplayController<ChipReceiverInfo, MediaTttLogger>(
context,
logger,
@@ -77,6 +79,7 @@
configurationController,
powerManager,
R.layout.media_ttt_chip_receiver,
+ wakeLockBuilder,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 47bed46..28da38b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -108,6 +108,7 @@
val filter = IntentFilter().apply {
addAction(Intent.ACTION_USER_SWITCHED)
+ addAction(Intent.ACTION_USER_INFO_CHANGED)
// These get called when a managed profile goes in or out of quiet mode.
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
@@ -125,6 +126,7 @@
Intent.ACTION_USER_SWITCHED -> {
handleSwitchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL))
}
+ Intent.ACTION_USER_INFO_CHANGED,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 073ab8b..f2b8603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -21,6 +21,7 @@
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -50,7 +51,6 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
-import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
@@ -74,12 +74,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.ViewClippingUtil;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -138,6 +138,7 @@
private final KeyguardStateController mKeyguardStateController;
protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final AuthController mAuthController;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -188,14 +189,7 @@
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
private boolean mDozing;
- private final ViewClippingUtil.ClippingParameters mClippingParams =
- new ViewClippingUtil.ClippingParameters() {
- @Override
- public boolean shouldFinish(View view) {
- return view == mIndicationArea;
- }
- };
- private ScreenLifecycle mScreenLifecycle;
+ private final ScreenLifecycle mScreenLifecycle;
private final ScreenLifecycle.Observer mScreenObserver =
new ScreenLifecycle.Observer() {
@Override
@@ -209,6 +203,7 @@
}
}
};
+ private boolean mFaceLockedOutThisAuthSession;
/**
* Creates a new KeyguardIndicationController and registers callbacks.
@@ -229,6 +224,7 @@
@Main DelayableExecutor executor,
@Background DelayableExecutor bgExecutor,
FalsingManager falsingManager,
+ AuthController authController,
LockPatternUtils lockPatternUtils,
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
@@ -248,6 +244,7 @@
mExecutor = executor;
mBackgroundExecutor = bgExecutor;
mLockPatternUtils = lockPatternUtils;
+ mAuthController = authController;
mFalsingManager = falsingManager;
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
@@ -619,7 +616,6 @@
if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
return;
}
- int currentUserId = getCurrentUser();
mDevicePolicyManager.logoutUser();
})
.build(),
@@ -676,7 +672,7 @@
hideTransientIndication();
}
updateDeviceEntryIndication(false);
- } else if (!visible) {
+ } else {
// If we unlock and return to keyguard quickly, previous error should not be shown
hideTransientIndication();
}
@@ -764,7 +760,7 @@
* logic.
*/
private void showBiometricMessage(CharSequence biometricMessage,
- CharSequence biometricMessageFollowUp) {
+ @Nullable CharSequence biometricMessageFollowUp) {
if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
return;
}
@@ -1072,17 +1068,12 @@
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
final boolean faceAuthFailed = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
- final boolean isUnlockWithFingerprintPossible =
- mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser());
+ final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
- if (DEBUG) {
- Log.d(TAG, "skip showing msgId=" + msgId + " helpString=" + helpString
- + ", due to co-ex logic");
- }
- return;
+ debugLog("skip showing msgId=" + msgId + " helpString=" + helpString
+ + ", due to co-ex logic");
} else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
mInitialTextColorState);
@@ -1120,74 +1111,45 @@
}
@Override
- public void onBiometricError(int msgId, String errString,
- BiometricSourceType biometricSourceType) {
- CharSequence deferredFaceMessage = null;
- if (biometricSourceType == FACE) {
- if (msgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) {
- deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
- if (DEBUG) {
- Log.d(TAG, "showDeferredFaceMessage msgId=" + deferredFaceMessage);
- }
- }
- mFaceAcquiredMessageDeferral.reset();
- }
-
- if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
- if (DEBUG) {
- Log.d(TAG, "suppressingBiometricError msgId=" + msgId
- + " source=" + biometricSourceType);
- }
- } else if (biometricSourceType == FACE && msgId == FaceManager.FACE_ERROR_TIMEOUT) {
- // Co-ex: show deferred message OR nothing
- if (mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- KeyguardUpdateMonitor.getCurrentUser())) {
- // if we're on the lock screen (bouncer isn't showing), show the deferred msg
- if (deferredFaceMessage != null
- && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
- showBiometricMessage(
- deferredFaceMessage,
- mContext.getString(R.string.keyguard_suggest_fingerprint)
- );
- return;
- }
-
- // otherwise, don't show any message
- if (DEBUG) {
- Log.d(TAG, "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
- }
- return;
- }
-
- // Face-only: The face timeout message is not very actionable, let's ask the user to
- // manually retry.
- if (deferredFaceMessage != null) {
- showBiometricMessage(
- deferredFaceMessage,
- mContext.getString(R.string.keyguard_unlock)
- );
- } else {
- // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
- showActionToUnlock();
- }
- } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
- mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
- } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
- showBiometricMessage(errString);
- } else {
- mBiometricErrorMessageToShowOnScreenOn = errString;
+ public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
+ mFaceLockedOutThisAuthSession = false;
}
}
- private boolean shouldSuppressBiometricError(int msgId,
- BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
- if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
- return shouldSuppressFingerprintError(msgId, updateMonitor);
- }
+ @Override
+ public void onBiometricError(int msgId, String errString,
+ BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE) {
- return shouldSuppressFaceError(msgId, updateMonitor);
+ onFaceAuthError(msgId, errString);
+ } else if (biometricSourceType == FINGERPRINT) {
+ onFingerprintAuthError(msgId, errString);
}
- return false;
+ }
+
+ private void onFaceAuthError(int msgId, String errString) {
+ CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
+ mFaceAcquiredMessageDeferral.reset();
+ if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
+ debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString);
+ return;
+ }
+ if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
+ handleFaceAuthTimeoutError(deferredFaceMessage);
+ } else if (isLockoutError(msgId)) {
+ handleFaceLockoutError(errString);
+ } else {
+ showErrorMessageNowOrLater(errString, null);
+ }
+ }
+
+ private void onFingerprintAuthError(int msgId, String errString) {
+ if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
+ debugLog("suppressingFingerprintError msgId=" + msgId
+ + " errString= " + errString);
+ } else {
+ showErrorMessageNowOrLater(errString, null);
+ }
}
private boolean shouldSuppressFingerprintError(int msgId,
@@ -1197,7 +1159,7 @@
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
// check of whether non-strong biometric is allowed
return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
+ && !isLockoutError(msgId))
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
@@ -1286,7 +1248,82 @@
}
}
- private StatusBarStateController.StateListener mStatusBarStateListener =
+ private void handleFaceLockoutError(String errString) {
+ int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+ : R.string.keyguard_unlock;
+ String followupMessage = mContext.getString(followupMsgId);
+ // Lockout error can happen multiple times in a session because we trigger face auth
+ // even when it is locked out so that the user is aware that face unlock would have
+ // triggered but didn't because it is locked out.
+
+ // On first lockout we show the error message from FaceManager, which tells the user they
+ // had too many unsuccessful attempts.
+ if (!mFaceLockedOutThisAuthSession) {
+ mFaceLockedOutThisAuthSession = true;
+ showErrorMessageNowOrLater(errString, followupMessage);
+ } else if (!mAuthController.isUdfpsFingerDown()) {
+ // On subsequent lockouts, we show a more generic locked out message.
+ showBiometricMessage(mContext.getString(R.string.keyguard_face_unlock_unavailable),
+ followupMessage);
+ }
+ }
+
+ private static boolean isLockoutError(int msgId) {
+ return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
+ || msgId == FaceManager.FACE_ERROR_LOCKOUT;
+ }
+
+ private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
+ debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
+ if (canUnlockWithFingerprint()) {
+ // Co-ex: show deferred message OR nothing
+ // if we're on the lock screen (bouncer isn't showing), show the deferred msg
+ if (deferredFaceMessage != null
+ && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ showBiometricMessage(
+ deferredFaceMessage,
+ mContext.getString(R.string.keyguard_suggest_fingerprint)
+ );
+ } else {
+ // otherwise, don't show any message
+ debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+ }
+ } else if (deferredFaceMessage != null) {
+ // Face-only: The face timeout message is not very actionable, let's ask the
+ // user to manually retry.
+ showBiometricMessage(
+ deferredFaceMessage,
+ mContext.getString(R.string.keyguard_unlock)
+ );
+ } else {
+ // Face-only
+ // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
+ showActionToUnlock();
+ }
+ }
+
+ private boolean canUnlockWithFingerprint() {
+ return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
+ KeyguardUpdateMonitor.getCurrentUser());
+ }
+
+ private void debugLog(String logMsg) {
+ if (DEBUG) {
+ Log.d(TAG, logMsg);
+ }
+ }
+
+ private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
+ if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
+ } else if (mScreenLifecycle.getScreenState() == SCREEN_ON) {
+ showBiometricMessage(errString, followUpMsg);
+ } else {
+ mBiometricErrorMessageToShowOnScreenOn = errString;
+ }
+ }
+
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onStateChanged(int newState) {
@@ -1307,7 +1344,7 @@
}
};
- private KeyguardStateController.Callback mKeyguardStateCallback =
+ private final KeyguardStateController.Callback mKeyguardStateCallback =
new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 408293c..815b86e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -805,7 +805,7 @@
iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow
&& ((ExpandableNotificationRow) view).isLowPriority()
- && mShelfIcons.hasMaxNumDot())
+ && mShelfIcons.areIconsOverflowing())
|| (transitionAmount == 0.0f && !iconState.isAnimating(icon))
|| row.isAboveShelf()
|| row.showingPulsing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index c189ace..4ee2de1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -141,7 +141,6 @@
/* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */
public static final int MAX_ICONS_ON_LOCKSCREEN = 3;
public static final int MAX_STATIC_ICONS = 4;
- private static final int MAX_DOTS = 1;
private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
@@ -166,8 +165,7 @@
private IconState mLastVisibleIconState;
private IconState mFirstVisibleIconState;
private float mVisualOverflowStart;
- // Keep track of overflow in range [0, 3]
- private int mNumDots;
+ private boolean mIsShowingOverflowDot;
private StatusBarIconView mIsolatedIcon;
private Rect mIsolatedIconLocation;
private int[] mAbsolutePosition = new int[2];
@@ -387,8 +385,8 @@
}
}
- public boolean hasMaxNumDot() {
- return mNumDots >= MAX_DOTS;
+ public boolean areIconsOverflowing() {
+ return mIsShowingOverflowDot;
}
private boolean areAnimationsEnabled(StatusBarIconView icon) {
@@ -494,7 +492,7 @@
: 1f;
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
- mNumDots = 0;
+ mIsShowingOverflowDot = false;
if (firstOverflowIndex != -1) {
translationX = mVisualOverflowStart;
for (int i = firstOverflowIndex; i < childCount; i++) {
@@ -502,15 +500,14 @@
IconState iconState = mIconStates.get(view);
int dotWidth = mStaticDotDiameter + mDotPadding;
iconState.setXTranslation(translationX);
- if (mNumDots < MAX_DOTS) {
- if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) {
+ if (!mIsShowingOverflowDot) {
+ if (iconState.iconAppearAmount < 0.8f) {
iconState.visibleState = StatusBarIconView.STATE_ICON;
} else {
iconState.visibleState = StatusBarIconView.STATE_DOT;
- mNumDots++;
+ mIsShowingOverflowDot = true;
}
- translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
- * iconState.iconAppearAmount;
+ translationX += dotWidth * iconState.iconAppearAmount;
mLastVisibleIconState = iconState;
} else {
iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
@@ -618,10 +615,6 @@
return Math.min(getWidth(), translation);
}
- private float getMaxOverflowStart() {
- return getLayoutEnd() - mIconSize;
- }
-
public void setChangingViewPositions(boolean changingViewPositions) {
mChangingViewPositions = changingViewPositions;
}
@@ -645,25 +638,6 @@
mSpeedBumpIndex = speedBumpIndex;
}
- public boolean hasOverflow() {
- return mNumDots > 0;
- }
-
- // Give some extra room for btw notifications if we can
- public int getNoOverflowExtraPadding() {
- if (mNumDots != 0) {
- return 0;
- }
-
- int collapsedPadding = mIconSize;
-
- if (collapsedPadding + getFinalTranslationX() > getWidth()) {
- collapsedPadding = getWidth() - getFinalTranslationX();
- }
-
- return collapsedPadding;
- }
-
public int getIconSize() {
return mIconSize;
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 637fac0..4cb41f3 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -22,7 +22,6 @@
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.PowerManager
-import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -35,6 +34,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.wakelock.WakeLock
/**
* A generic controller that can temporarily display a new view in a new window.
@@ -54,6 +54,7 @@
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
+ private val wakeLockBuilder: WakeLock.Builder,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -64,7 +65,8 @@
height = WindowManager.LayoutParams.WRAP_CONTENT
type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -84,6 +86,15 @@
private var cancelViewTimeout: Runnable? = null
/**
+ * A wakelock that is acquired when view is displayed and screen off,
+ * then released when view is removed.
+ */
+ private var wakeLock: WakeLock? = null
+
+ /** A string that keeps track of wakelock reason once it is acquired till it gets released */
+ private var wakeReasonAcquired: String? = null
+
+ /**
* Displays the view with the provided [newInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
@@ -113,11 +124,15 @@
// the view to show over the dream state, so we should only wake up if the screen is
// completely off.)
if (!powerManager.isScreenOn) {
- powerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:${newInfo.wakeReason}",
- )
+ wakeLock = wakeLockBuilder
+ .setTag(newInfo.windowTitle)
+ .setLevelsAndFlags(
+ PowerManager.FULL_WAKE_LOCK or
+ PowerManager.ACQUIRE_CAUSES_WAKEUP
+ )
+ .build()
+ wakeLock?.acquire(newInfo.wakeReason)
+ wakeReasonAcquired = newInfo.wakeReason
}
logger.logViewAddition(newInfo.windowTitle)
inflateAndUpdateView(newInfo)
@@ -155,6 +170,7 @@
it.copyFrom(windowLayoutParams)
it.title = newInfo.windowTitle
}
+ newView.keepScreenOn = true
windowManager.addView(newView, paramsWithTitle)
animateViewIn(newView)
}
@@ -183,7 +199,10 @@
val currentDisplayInfo = displayInfo ?: return
val currentView = currentDisplayInfo.view
- animateViewOut(currentView) { windowManager.removeView(currentView) }
+ animateViewOut(currentView) {
+ windowManager.removeView(currentView)
+ wakeLock?.release(wakeReasonAcquired)
+ }
logger.logViewRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 87b6e8d..44e5ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -44,6 +44,7 @@
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
import javax.inject.Inject
/**
@@ -75,6 +76,7 @@
private val falsingCollector: FalsingCollector,
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
+ wakeLockBuilder: WakeLock.Builder,
) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
@@ -84,6 +86,7 @@
configurationController,
powerManager,
R.layout.chipbar,
+ wakeLockBuilder,
) {
private lateinit var parent: ChipbarRootView
@@ -92,8 +95,6 @@
gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
}
- override fun start() {}
-
override fun updateView(
newInfo: ChipbarInfo,
currentView: ViewGroup
@@ -192,6 +193,8 @@
)
}
+ override fun start() {}
+
override fun getTouchableRegion(view: View, outRect: Rect) {
viewUtil.setRectToViewWindowLocation(view, outRect)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index f017126..b56c403 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -25,6 +25,8 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -59,6 +61,7 @@
private final ActivityStarter mActivityStarter;
private Dialog mSetupUserDialog;
+ private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
@Inject
public CreateUserActivity(UserCreator userCreator,
@@ -82,6 +85,10 @@
mSetupUserDialog = createDialog();
mSetupUserDialog.show();
+
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mBackCallback);
}
@Override
@@ -125,10 +132,20 @@
@Override
public void onBackPressed() {
- super.onBackPressed();
+ onBackInvoked();
+ }
+
+ private void onBackInvoked() {
if (mSetupUserDialog != null) {
mSetupUserDialog.dismiss();
}
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
+ super.onDestroy();
}
private void addUserNow(String userName, Drawable userIcon) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 6a23260..ffaf524 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -250,6 +250,10 @@
override fun onUserChanged(newUser: Int, userContext: Context) {
send()
}
+
+ override fun onProfilesChanged(profiles: List<UserInfo>) {
+ send()
+ }
}
tracker.addCallback(callback, mainDispatcher.asExecutor())
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 8d77c4a..f320d07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -38,6 +38,11 @@
long DEFAULT_MAX_TIMEOUT = 20000;
/**
+ * Default wake-lock levels and flags.
+ */
+ int DEFAULT_LEVELS_AND_FLAGS = PowerManager.PARTIAL_WAKE_LOCK;
+
+ /**
* @param why A tag that will be saved for sysui dumps.
* @see android.os.PowerManager.WakeLock#acquire()
**/
@@ -60,13 +65,21 @@
* Creates a {@link WakeLock} that has a default release timeout.
* @see android.os.PowerManager.WakeLock#acquire(long) */
static WakeLock createPartial(Context context, String tag, long maxTimeout) {
- return wrap(createPartialInner(context, tag), maxTimeout);
+ return wrap(createWakeLockInner(context, tag, DEFAULT_LEVELS_AND_FLAGS), maxTimeout);
+ }
+
+ /**
+ * Creates a {@link WakeLock} that has a default release timeout and flags.
+ */
+ static WakeLock createWakeLock(Context context, String tag, int flags, long maxTimeout) {
+ return wrap(createWakeLockInner(context, tag, flags), maxTimeout);
}
@VisibleForTesting
- static PowerManager.WakeLock createPartialInner(Context context, String tag) {
+ static PowerManager.WakeLock createWakeLockInner(
+ Context context, String tag, int levelsAndFlags) {
return context.getSystemService(PowerManager.class)
- .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
+ .newWakeLock(levelsAndFlags, tag);
}
static Runnable wrapImpl(WakeLock w, Runnable r) {
@@ -131,6 +144,7 @@
class Builder {
private final Context mContext;
private String mTag;
+ private int mLevelsAndFlags = DEFAULT_LEVELS_AND_FLAGS;
private long mMaxTimeout = DEFAULT_MAX_TIMEOUT;
@Inject
@@ -143,13 +157,18 @@
return this;
}
+ public Builder setLevelsAndFlags(int levelsAndFlags) {
+ this.mLevelsAndFlags = levelsAndFlags;
+ return this;
+ }
+
public Builder setMaxTimeout(long maxTimeout) {
this.mMaxTimeout = maxTimeout;
return this;
}
public WakeLock build() {
- return WakeLock.createPartial(mContext, mTag, mMaxTimeout);
+ return WakeLock.createWakeLock(mContext, mTag, mLevelsAndFlags, mMaxTimeout);
}
}
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 1b404a8..e51236b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -106,6 +106,12 @@
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true" />
+ <activity android:name=".user.CreateUserActivityTest$CreateUserActivityTestable"
+ android:exported="false"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true" />
+
<provider
android:name="androidx.startup.InitializationProvider"
tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 0a2b3d8..aa4469f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -54,7 +54,8 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.biometrics.SideFpsController;
+import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
@@ -141,7 +142,7 @@
@Mock
private KeyguardViewController mKeyguardViewController;
@Mock
- private SidefpsController mSidefpsController;
+ private SideFpsController mSideFpsController;
@Mock
private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
@Mock
@@ -189,7 +190,7 @@
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
- mSessionTracker, Optional.of(mSidefpsController), mFalsingA11yDelegate).create(
+ mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate).create(
mSecurityCallback);
}
@@ -345,48 +346,48 @@
@Test
public void onBouncerVisibilityChanged_allConditionsGood_sideFpsHintShown() {
setupConditionsToEnableSideFpsHint();
- reset(mSidefpsController);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController).show();
- verify(mSidefpsController, never()).hide();
+ verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).hide(any());
}
@Test
public void onBouncerVisibilityChanged_fpsSensorNotRunning_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
setFingerprintDetectionRunning(false);
- reset(mSidefpsController);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
setSideFpsHintEnabledFromResources(false);
- reset(mSidefpsController);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
setNeedsStrongAuth(true);
- reset(mSidefpsController);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
@@ -394,13 +395,13 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController, atLeastOnce()).show();
- reset(mSidefpsController);
+ verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
@@ -408,13 +409,13 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController, atLeastOnce()).show();
- reset(mSidefpsController);
+ verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onStartingToHide();
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
@@ -422,13 +423,13 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSidefpsController, atLeastOnce()).show();
- reset(mSidefpsController);
+ verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onPause();
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
@@ -436,12 +437,12 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- reset(mSidefpsController);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onResume(0);
- verify(mSidefpsController).show();
- verify(mSidefpsController, never()).hide();
+ verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).hide(any());
}
@Test
@@ -450,12 +451,12 @@
setupConditionsToEnableSideFpsHint();
setSideFpsHintEnabledFromResources(false);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- reset(mSidefpsController);
+ reset(mSideFpsController);
mKeyguardSecurityContainerController.onResume(0);
- verify(mSidefpsController).hide();
- verify(mSidefpsController, never()).show();
+ verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, never()).show(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a8284d2..9c14ee6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1621,7 +1621,7 @@
}
@Test
- public void testShouldListenForFace_whenFaceIsLockedOut_returnsFalse()
+ public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
throws RemoteException {
// Preconditions for face auth to run
keyguardNotGoingAway();
@@ -1638,7 +1638,9 @@
faceAuthLockedOut();
mTestableLooper.processAllMessages();
- assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ // This is needed beccause we want to show face locked out error message whenever face auth
+ // is supposed to run.
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
}
@Test
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 a275c8d..83bf183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -145,7 +145,7 @@
@Mock
private UdfpsController mUdfpsController;
@Mock
- private SidefpsController mSidefpsController;
+ private SideFpsController mSideFpsController;
@Mock
private DisplayManager mDisplayManager;
@Mock
@@ -225,7 +225,7 @@
mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
- () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController,
+ () -> mUdfpsController, () -> mSideFpsController, mStatusBarStateController,
mVibratorHelper);
mAuthController.start();
@@ -256,7 +256,7 @@
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
- mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+ mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
mStatusBarStateController, mVibratorHelper);
authController.start();
@@ -282,7 +282,7 @@
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
- mFaceManager, () -> mUdfpsController, () -> mSidefpsController,
+ mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
mStatusBarStateController, mVibratorHelper);
authController.start();
@@ -936,7 +936,7 @@
FingerprintManager fingerprintManager,
FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory,
- Provider<SidefpsController> sidefpsControllerFactory,
+ Provider<SideFpsController> sidefpsControllerFactory,
StatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper) {
super(context, execution, commandQueue, activityTaskManager, windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
new file mode 100644
index 0000000..e7d5632
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2021 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.animation.Animator
+import android.app.ActivityManager
+import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.graphics.Insets
+import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManagerGlobal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+import android.view.DisplayInfo
+import android.view.LayoutInflater
+import android.view.Surface
+import android.view.View
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+private const val SENSOR_ID = 1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SideFpsControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule var rule = MockitoJUnit.rule()
+
+ @Mock lateinit var layoutInflater: LayoutInflater
+ @Mock lateinit var fingerprintManager: FingerprintManager
+ @Mock lateinit var windowManager: WindowManager
+ @Mock lateinit var activityTaskManager: ActivityTaskManager
+ @Mock lateinit var sideFpsView: View
+ @Mock lateinit var displayManager: DisplayManager
+ @Mock lateinit var overviewProxyService: OverviewProxyService
+ @Mock lateinit var handler: Handler
+ @Mock lateinit var dumpManager: DumpManager
+ @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
+ @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var overlayController: ISidefpsController
+ private lateinit var sideFpsController: SideFpsController
+
+ enum class DeviceConfig {
+ X_ALIGNED,
+ Y_ALIGNED_UNFOLDED,
+ Y_ALIGNED_FOLDED
+ }
+
+ private lateinit var deviceConfig: DeviceConfig
+ private lateinit var indicatorBounds: Rect
+ private lateinit var displayBounds: Rect
+ private lateinit var sensorLocation: SensorLocationInternal
+ private var displayWidth: Int = 0
+ private var displayHeight: Int = 0
+ private var boundsWidth: Int = 0
+ private var boundsHeight: Int = 0
+
+ @Before
+ fun setup() {
+ context.addMockSystemService(DisplayManager::class.java, displayManager)
+ context.addMockSystemService(WindowManager::class.java, windowManager)
+
+ whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
+ whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+ .thenReturn(mock(LottieAnimationView::class.java))
+ with(mock(ViewPropertyAnimator::class.java)) {
+ whenEver(sideFpsView.animate()).thenReturn(this)
+ whenEver(alpha(anyFloat())).thenReturn(this)
+ whenEver(setStartDelay(anyLong())).thenReturn(this)
+ whenEver(setDuration(anyLong())).thenReturn(this)
+ whenEver(setListener(any())).thenAnswer {
+ (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd(
+ mock(Animator::class.java)
+ )
+ this
+ }
+ }
+ }
+
+ private fun testWithDisplay(
+ deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
+ initInfo: DisplayInfo.() -> Unit = {},
+ windowInsets: WindowInsets = insetsForSmallNavbar(),
+ block: () -> Unit
+ ) {
+ this.deviceConfig = deviceConfig
+
+ when (deviceConfig) {
+ DeviceConfig.X_ALIGNED -> {
+ displayWidth = 2560
+ displayHeight = 1600
+ sensorLocation = SensorLocationInternal("", 2325, 0, 0)
+ boundsWidth = 160
+ boundsHeight = 84
+ }
+ DeviceConfig.Y_ALIGNED_UNFOLDED -> {
+ displayWidth = 2208
+ displayHeight = 1840
+ sensorLocation = SensorLocationInternal("", 0, 510, 0)
+ boundsWidth = 110
+ boundsHeight = 210
+ }
+ DeviceConfig.Y_ALIGNED_FOLDED -> {
+ displayWidth = 1080
+ displayHeight = 2100
+ sensorLocation = SensorLocationInternal("", 0, 590, 0)
+ boundsWidth = 110
+ boundsHeight = 210
+ }
+ }
+ indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
+ displayBounds = Rect(0, 0, displayWidth, displayHeight)
+ var locations = listOf(sensorLocation)
+
+ whenEver(fingerprintManager.sensorPropertiesInternal)
+ .thenReturn(
+ listOf(
+ FingerprintSensorPropertiesInternal(
+ SENSOR_ID,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ listOf() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_POWER_BUTTON,
+ true /* halControlsIllumination */,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ locations
+ )
+ )
+ )
+
+ val displayInfo = DisplayInfo()
+ displayInfo.initInfo()
+ val dmGlobal = mock(DisplayManagerGlobal::class.java)
+ val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
+ whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
+ whenEver(windowManager.defaultDisplay).thenReturn(display)
+ whenEver(windowManager.maximumWindowMetrics)
+ .thenReturn(WindowMetrics(displayBounds, WindowInsets.CONSUMED))
+ whenEver(windowManager.currentWindowMetrics)
+ .thenReturn(WindowMetrics(displayBounds, windowInsets))
+
+ sideFpsController =
+ SideFpsController(
+ context.createDisplayContext(display),
+ layoutInflater,
+ fingerprintManager,
+ windowManager,
+ activityTaskManager,
+ overviewProxyService,
+ displayManager,
+ executor,
+ handler,
+ dumpManager
+ )
+
+ overlayController =
+ ArgumentCaptor.forClass(ISidefpsController::class.java)
+ .apply { verify(fingerprintManager).setSidefpsController(capture()) }
+ .value
+
+ block()
+ }
+
+ @Test
+ fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong())
+
+ overlayController.hide(SENSOR_ID)
+ executor.runAllReady()
+ verify(displayManager).unregisterDisplayListener(any())
+ }
+
+ @Test
+ fun testShowsAndHides() = testWithDisplay {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).addView(overlayCaptor.capture(), any())
+
+ reset(windowManager)
+ overlayController.hide(SENSOR_ID)
+ executor.runAllReady()
+
+ verify(windowManager, never()).addView(any(), any())
+ verify(windowManager).removeView(eq(overlayCaptor.value))
+ }
+
+ @Test
+ fun testShowsOnce() = testWithDisplay {
+ repeat(5) {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+ }
+
+ verify(windowManager).addView(any(), any())
+ verify(windowManager, never()).removeView(any())
+ }
+
+ @Test
+ fun testHidesOnce() = testWithDisplay {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ repeat(5) {
+ overlayController.hide(SENSOR_ID)
+ executor.runAllReady()
+ }
+
+ verify(windowManager).addView(any(), any())
+ verify(windowManager).removeView(any())
+ }
+
+ @Test fun testIgnoredForKeyguard() = testWithDisplay { testIgnoredFor(REASON_AUTH_KEYGUARD) }
+
+ @Test
+ fun testShowsForMostSettings() = testWithDisplay {
+ whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
+ testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
+ }
+
+ @Test
+ fun testIgnoredForVerySpecificSettings() = testWithDisplay {
+ whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
+ testIgnoredFor(REASON_AUTH_SETTINGS)
+ }
+
+ private fun testIgnoredFor(reason: Int, ignored: Boolean = true) {
+ overlayController.show(SENSOR_ID, reason)
+ executor.runAllReady()
+
+ verify(windowManager, if (ignored) never() else times(1)).addView(any(), any())
+ }
+
+ @Test
+ fun showsWithTaskbar() =
+ testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_0 }) {
+ hidesWithTaskbar(visible = true)
+ }
+
+ @Test
+ fun showsWithTaskbarOnY() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+ { rotation = Surface.ROTATION_0 }
+ ) { hidesWithTaskbar(visible = true) }
+
+ @Test
+ fun showsWithTaskbar90() =
+ testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED, { rotation = Surface.ROTATION_90 }) {
+ hidesWithTaskbar(visible = true)
+ }
+
+ @Test
+ fun showsWithTaskbar90OnY() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+ { rotation = Surface.ROTATION_90 }
+ ) { hidesWithTaskbar(visible = true) }
+
+ @Test
+ fun showsWithTaskbar180() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ { rotation = Surface.ROTATION_180 }
+ ) { hidesWithTaskbar(visible = true) }
+
+ @Test
+ fun showsWithTaskbar270OnY() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+ { rotation = Surface.ROTATION_270 }
+ ) { hidesWithTaskbar(visible = true) }
+
+ @Test
+ fun showsWithTaskbarCollapsedDown() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ { rotation = Surface.ROTATION_270 },
+ windowInsets = insetsForSmallNavbar()
+ ) { hidesWithTaskbar(visible = true) }
+
+ @Test
+ fun showsWithTaskbarCollapsedDownOnY() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+ { rotation = Surface.ROTATION_180 },
+ windowInsets = insetsForSmallNavbar()
+ ) { hidesWithTaskbar(visible = true) }
+
+ @Test
+ fun hidesWithTaskbarDown() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.X_ALIGNED,
+ { rotation = Surface.ROTATION_180 },
+ windowInsets = insetsForLargeNavbar()
+ ) { hidesWithTaskbar(visible = false) }
+
+ @Test
+ fun hidesWithTaskbarDownOnY() =
+ testWithDisplay(
+ deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
+ { rotation = Surface.ROTATION_270 },
+ windowInsets = insetsForLargeNavbar()
+ ) { hidesWithTaskbar(visible = true) }
+
+ private fun hidesWithTaskbar(visible: Boolean) {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
+ executor.runAllReady()
+
+ verify(windowManager).addView(any(), any())
+ verify(windowManager, never()).removeView(any())
+ verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE
+ }
+
+ @Test
+ fun testIndicatorPlacementForXAlignedSensor() =
+ testWithDisplay(deviceConfig = DeviceConfig.X_ALIGNED) {
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
+ assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
+ }
+
+ @Test
+ fun testIndicatorPlacementForYAlignedSensor() =
+ testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+ assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
+ assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
+ }
+
+ @Test
+ fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
+ // By default all those tests assume the side fps sensor is available.
+
+ assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
+ }
+
+ @Test
+ fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
+ whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
+
+ assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
+ }
+
+ @Test
+ fun testLayoutParams_hasNoMoveAnimationWindowFlag() =
+ testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
+ }
+
+ @Test
+ fun testLayoutParams_hasTrustedOverlayWindowFlag() =
+ testWithDisplay(deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED) {
+ sideFpsController.overlayOffsets = sensorLocation
+ sideFpsController.updateOverlayParams(windowManager.defaultDisplay, indicatorBounds)
+ overlayController.show(SENSOR_ID, REASON_UNKNOWN)
+ executor.runAllReady()
+
+ verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
+
+ val lpFlags = overlayViewParamsCaptor.value.privateFlags
+
+ assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
+ }
+}
+
+private fun insetsForSmallNavbar() = insetsWithBottom(60)
+
+private fun insetsForLargeNavbar() = insetsWithBottom(100)
+
+private fun insetsWithBottom(bottom: Int) =
+ WindowInsets.Builder()
+ .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
+ .build()
+
+private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling")
+
+private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
+
+private fun settingsTask(cls: String) =
+ ActivityManager.RunningTaskInfo().apply {
+ topActivity = ComponentName.createRelative("com.android.settings", cls)
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
deleted file mode 100644
index 8d969d0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ /dev/null
@@ -1,493 +0,0 @@
-/*
- * Copyright (C) 2021 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.animation.Animator
-import android.app.ActivityManager
-import android.app.ActivityTaskManager
-import android.content.ComponentName
-import android.graphics.Insets
-import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
-import android.hardware.biometrics.SensorLocationInternal
-import android.hardware.biometrics.SensorProperties
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManagerGlobal
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.hardware.fingerprint.ISidefpsController
-import android.os.Handler
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.Display
-import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
-import android.view.DisplayInfo
-import android.view.LayoutInflater
-import android.view.Surface
-import android.view.View
-import android.view.ViewPropertyAnimator
-import android.view.WindowInsets
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
-import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
-import android.view.WindowMetrics
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.recents.OverviewProxyService
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyLong
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val DISPLAY_ID = 2
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-class SidefpsControllerTest : SysuiTestCase() {
-
- @JvmField @Rule
- var rule = MockitoJUnit.rule()
-
- @Mock
- lateinit var layoutInflater: LayoutInflater
- @Mock
- lateinit var fingerprintManager: FingerprintManager
- @Mock
- lateinit var windowManager: WindowManager
- @Mock
- lateinit var activityTaskManager: ActivityTaskManager
- @Mock
- lateinit var sidefpsView: View
- @Mock
- lateinit var displayManager: DisplayManager
- @Mock
- lateinit var overviewProxyService: OverviewProxyService
- @Mock
- lateinit var handler: Handler
- @Captor
- lateinit var overlayCaptor: ArgumentCaptor<View>
- @Captor
- lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
-
- private val executor = FakeExecutor(FakeSystemClock())
- private lateinit var overlayController: ISidefpsController
- private lateinit var sideFpsController: SidefpsController
-
- enum class DeviceConfig { X_ALIGNED, Y_ALIGNED_UNFOLDED, Y_ALIGNED_FOLDED }
-
- private lateinit var deviceConfig: DeviceConfig
- private lateinit var indicatorBounds: Rect
- private lateinit var displayBounds: Rect
- private lateinit var sensorLocation: SensorLocationInternal
- private var displayWidth: Int = 0
- private var displayHeight: Int = 0
- private var boundsWidth: Int = 0
- private var boundsHeight: Int = 0
-
- @Before
- fun setup() {
- context.addMockSystemService(DisplayManager::class.java, displayManager)
- context.addMockSystemService(WindowManager::class.java, windowManager)
-
- whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
- whenEver(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
- .thenReturn(mock(LottieAnimationView::class.java))
- with(mock(ViewPropertyAnimator::class.java)) {
- whenEver(sidefpsView.animate()).thenReturn(this)
- whenEver(alpha(anyFloat())).thenReturn(this)
- whenEver(setStartDelay(anyLong())).thenReturn(this)
- whenEver(setDuration(anyLong())).thenReturn(this)
- whenEver(setListener(any())).thenAnswer {
- (it.arguments[0] as Animator.AnimatorListener)
- .onAnimationEnd(mock(Animator::class.java))
- this
- }
- }
- }
-
- private fun testWithDisplay(
- deviceConfig: DeviceConfig = DeviceConfig.X_ALIGNED,
- initInfo: DisplayInfo.() -> Unit = {},
- windowInsets: WindowInsets = insetsForSmallNavbar(),
- block: () -> Unit
- ) {
- this.deviceConfig = deviceConfig
-
- when (deviceConfig) {
- DeviceConfig.X_ALIGNED -> {
- displayWidth = 2560
- displayHeight = 1600
- sensorLocation = SensorLocationInternal("", 2325, 0, 0)
- boundsWidth = 160
- boundsHeight = 84
- }
- DeviceConfig.Y_ALIGNED_UNFOLDED -> {
- displayWidth = 2208
- displayHeight = 1840
- sensorLocation = SensorLocationInternal("", 0, 510, 0)
- boundsWidth = 110
- boundsHeight = 210
- }
- DeviceConfig.Y_ALIGNED_FOLDED -> {
- displayWidth = 1080
- displayHeight = 2100
- sensorLocation = SensorLocationInternal("", 0, 590, 0)
- boundsWidth = 110
- boundsHeight = 210
- }
- }
- indicatorBounds = Rect(0, 0, boundsWidth, boundsHeight)
- displayBounds = Rect(0, 0, displayWidth, displayHeight)
- var locations = listOf(sensorLocation)
-
- whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(
- listOf(
- FingerprintSensorPropertiesInternal(
- SENSOR_ID,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- listOf() /* componentInfo */,
- FingerprintSensorProperties.TYPE_POWER_BUTTON,
- true /* halControlsIllumination */,
- true /* resetLockoutRequiresHardwareAuthToken */,
- locations
- )
- )
- )
-
- val displayInfo = DisplayInfo()
- displayInfo.initInfo()
- val dmGlobal = mock(DisplayManagerGlobal::class.java)
- val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
- whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
- whenEver(windowManager.defaultDisplay).thenReturn(display)
- whenEver(windowManager.maximumWindowMetrics).thenReturn(
- WindowMetrics(displayBounds, WindowInsets.CONSUMED)
- )
- whenEver(windowManager.currentWindowMetrics).thenReturn(
- WindowMetrics(displayBounds, windowInsets)
- )
-
- sideFpsController = SidefpsController(
- context.createDisplayContext(display), layoutInflater, fingerprintManager,
- windowManager, activityTaskManager, overviewProxyService, displayManager, executor,
- handler
- )
-
- overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply {
- verify(fingerprintManager).setSidefpsController(capture())
- }.value
-
- block()
- }
-
- @Test
- fun testSubscribesToOrientationChangesWhenShowingOverlay() = testWithDisplay {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(displayManager).registerDisplayListener(any(), eq(handler), anyLong())
-
- overlayController.hide(SENSOR_ID)
- executor.runAllReady()
- verify(displayManager).unregisterDisplayListener(any())
- }
-
- @Test
- fun testShowsAndHides() = testWithDisplay {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).addView(overlayCaptor.capture(), any())
-
- reset(windowManager)
- overlayController.hide(SENSOR_ID)
- executor.runAllReady()
-
- verify(windowManager, never()).addView(any(), any())
- verify(windowManager).removeView(eq(overlayCaptor.value))
- }
-
- @Test
- fun testShowsOnce() = testWithDisplay {
- repeat(5) {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
- }
-
- verify(windowManager).addView(any(), any())
- verify(windowManager, never()).removeView(any())
- }
-
- @Test
- fun testHidesOnce() = testWithDisplay {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- repeat(5) {
- overlayController.hide(SENSOR_ID)
- executor.runAllReady()
- }
-
- verify(windowManager).addView(any(), any())
- verify(windowManager).removeView(any())
- }
-
- @Test
- fun testIgnoredForKeyguard() = testWithDisplay {
- testIgnoredFor(REASON_AUTH_KEYGUARD)
- }
-
- @Test
- fun testShowsForMostSettings() = testWithDisplay {
- whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
- testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
- }
-
- @Test
- fun testIgnoredForVerySpecificSettings() = testWithDisplay {
- whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
- testIgnoredFor(REASON_AUTH_SETTINGS)
- }
-
- private fun testIgnoredFor(reason: Int, ignored: Boolean = true) {
- overlayController.show(SENSOR_ID, reason)
- executor.runAllReady()
-
- verify(windowManager, if (ignored) never() else times(1)).addView(any(), any())
- }
-
- @Test
- fun showsWithTaskbar() = testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- { rotation = Surface.ROTATION_0 }
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbarOnY() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
- { rotation = Surface.ROTATION_0 }
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbar90() = testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- { rotation = Surface.ROTATION_90 }
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbar90OnY() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
- { rotation = Surface.ROTATION_90 }
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbar180() = testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- { rotation = Surface.ROTATION_180 }
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbar270OnY() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
- { rotation = Surface.ROTATION_270 }
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbarCollapsedDown() = testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- { rotation = Surface.ROTATION_270 },
- windowInsets = insetsForSmallNavbar()
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
- { rotation = Surface.ROTATION_180 },
- windowInsets = insetsForSmallNavbar()
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- @Test
- fun hidesWithTaskbarDown() = testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED,
- { rotation = Surface.ROTATION_180 },
- windowInsets = insetsForLargeNavbar()
- ) {
- hidesWithTaskbar(visible = false)
- }
-
- @Test
- fun hidesWithTaskbarDownOnY() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED,
- { rotation = Surface.ROTATION_270 },
- windowInsets = insetsForLargeNavbar()
- ) {
- hidesWithTaskbar(visible = true)
- }
-
- private fun hidesWithTaskbar(visible: Boolean) {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(visible, false)
- executor.runAllReady()
-
- verify(windowManager).addView(any(), any())
- verify(windowManager, never()).removeView(any())
- verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
- }
-
- @Test
- fun testIndicatorPlacementForXAlignedSensor() = testWithDisplay(
- deviceConfig = DeviceConfig.X_ALIGNED
- ) {
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(
- windowManager.defaultDisplay,
- indicatorBounds
- )
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
- }
-
- @Test
- fun testIndicatorPlacementForYAlignedSensor() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
- ) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(
- windowManager.defaultDisplay,
- indicatorBounds
- )
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
- assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
- assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
- }
-
- @Test
- fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
- // By default all those tests assume the side fps sensor is available.
-
- assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
- }
-
- @Test
- fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
- whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
-
- assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
- }
-
- @Test
- fun testLayoutParams_hasNoMoveAnimationWindowFlag() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
- ) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(
- windowManager.defaultDisplay,
- indicatorBounds
- )
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_NO_MOVE_ANIMATION) != 0).isTrue()
- }
-
- @Test
- fun testLayoutParams_hasTrustedOverlayWindowFlag() = testWithDisplay(
- deviceConfig = DeviceConfig.Y_ALIGNED_UNFOLDED
- ) {
- sideFpsController.overlayOffsets = sensorLocation
- sideFpsController.updateOverlayParams(
- windowManager.defaultDisplay,
- indicatorBounds
- )
- overlayController.show(SENSOR_ID, REASON_UNKNOWN)
- executor.runAllReady()
-
- verify(windowManager).updateViewLayout(any(), overlayViewParamsCaptor.capture())
-
- val lpFlags = overlayViewParamsCaptor.value.privateFlags
-
- assertThat((lpFlags and PRIVATE_FLAG_TRUSTED_OVERLAY) != 0).isTrue()
- }
-}
-
-private fun insetsForSmallNavbar() = insetsWithBottom(60)
-private fun insetsForLargeNavbar() = insetsWithBottom(100)
-private fun insetsWithBottom(bottom: Int) = WindowInsets.Builder()
- .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
- .build()
-
-private fun fpEnrollTask() = settingsTask(".biometrics.fingerprint.FingerprintEnrollEnrolling")
-private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
-private fun settingsTask(cls: String) = ActivityManager.RunningTaskInfo().apply {
- topActivity = ComponentName.createRelative("com.android.settings", cls)
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index be39c0d..25f2602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -20,6 +20,8 @@
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
+import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -108,9 +110,6 @@
@RunWithLooper(setAsMainLooper = true)
public class UdfpsControllerTest extends SysuiTestCase {
- // Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things
- // leaving SystemUI.
- private static final int TEST_UDFPS_SENSOR_ID = 1;
private static final long TEST_REQUEST_ID = 70;
@Rule
@@ -121,7 +120,6 @@
// Dependencies
private FakeExecutor mBiometricsExecutor;
- private Execution mExecution;
@Mock
private LayoutInflater mLayoutInflater;
@Mock
@@ -129,8 +127,6 @@
@Mock
private WindowManager mWindowManager;
@Mock
- private UdfpsDisplayModeProvider mDisplayModeProvider;
- @Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -199,17 +195,24 @@
private BouncerInteractor mBouncerInteractor;
// Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
+ @Captor
+ private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
private IUdfpsOverlayController mOverlayController;
- @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
- @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
- @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
- @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
+ @Captor
+ private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+ @Captor
+ private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
+ @Captor
+ private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
+ @Captor
+ private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
private ScreenLifecycle.Observer mScreenObserver;
+ private FingerprintSensorPropertiesInternal mOpticalProps;
+ private FingerprintSensorPropertiesInternal mUltrasonicProps;
@Before
public void setUp() {
- mExecution = new FakeExecution();
+ Execution execution = new FakeExecution();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
.thenReturn(mUdfpsView);
@@ -222,9 +225,7 @@
when(mLayoutInflater.inflate(R.layout.udfps_fpm_other_view, null))
.thenReturn(mFpmOtherView);
when(mEnrollView.getContext()).thenReturn(mContext);
- when(mKeyguardStateController.isOccluded()).thenReturn(false);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
- final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
@@ -234,13 +235,25 @@
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
"vendor/version/revision" /* softwareVersion */));
- props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID,
+ mOpticalProps = new FingerprintSensorPropertiesInternal(1 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- true /* resetLockoutRequiresHardwareAuthToken */));
+ true /* resetLockoutRequiresHardwareAuthToken */);
+
+ mUltrasonicProps = new FingerprintSensorPropertiesInternal(2 /* sensorId */,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+
+ List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+ props.add(mOpticalProps);
+ props.add(mUltrasonicProps);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+
mFgExecutor = new FakeExecutor(new FakeSystemClock());
// Create a fake background executor.
@@ -248,7 +261,7 @@
mUdfpsController = new UdfpsController(
mContext,
- mExecution,
+ execution,
mLayoutInflater,
mFingerprintManager,
mWindowManager,
@@ -283,13 +296,13 @@
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
mScreenObserver = mScreenObserverCaptor.getValue();
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams());
+ mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams());
mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
}
@Test
public void dozeTimeTick() throws RemoteException {
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
mUdfpsController.dozeTimeTick();
@@ -304,7 +317,7 @@
when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -339,7 +352,7 @@
when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -347,7 +360,7 @@
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
if (stale) {
- mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
}
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
@@ -367,7 +380,7 @@
when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -390,12 +403,12 @@
@Test
public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
// GIVEN overlay was showing and the udfps bouncer is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
// WHEN the overlay is hidden
- mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
// THEN the udfps bouncer is reset
@@ -404,13 +417,13 @@
@Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong());
- mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
mFgExecutor.runAllReady();
verify(mDisplayManager).unregisterDisplayListener(any());
@@ -426,37 +439,44 @@
final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0],
displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
- for (int i1 = 0; i1 <= 1; ++i1)
- for (int i2 = 0; i2 <= 1; ++i2)
- for (int i3 = 0; i3 <= 1; ++i3)
- for (int i4 = 0; i4 <= 1; ++i4)
- for (int i5 = 0; i5 <= 1; ++i5) {
- final UdfpsOverlayParams newParams = new UdfpsOverlayParams(sensorBounds[i1],
- displayWidth[i2], displayHeight[i3], scaleFactor[i4], rotation[i5]);
+ for (int i1 = 0; i1 <= 1; ++i1) {
+ for (int i2 = 0; i2 <= 1; ++i2) {
+ for (int i3 = 0; i3 <= 1; ++i3) {
+ for (int i4 = 0; i4 <= 1; ++i4) {
+ for (int i5 = 0; i5 <= 1; ++i5) {
+ final UdfpsOverlayParams newParams = new UdfpsOverlayParams(
+ sensorBounds[i1],
+ displayWidth[i2], displayHeight[i3], scaleFactor[i4],
+ rotation[i5]);
- if (newParams.equals(oldParams)) {
- continue;
+ if (newParams.equals(oldParams)) {
+ continue;
+ }
+
+ // Initialize the overlay with old parameters.
+ mUdfpsController.updateOverlayParams(mOpticalProps, oldParams);
+
+ // Show the overlay.
+ reset(mWindowManager);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID,
+ mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_ENROLL_ENROLLING,
+ mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+ verify(mWindowManager).addView(any(), any());
+
+ // Update overlay parameters.
+ reset(mWindowManager);
+ mUdfpsController.updateOverlayParams(mOpticalProps, newParams);
+ mFgExecutor.runAllReady();
+
+ // Ensure the overlay was recreated.
+ verify(mWindowManager).removeView(any());
+ verify(mWindowManager).addView(any(), any());
+ }
+ }
+ }
}
-
- // Initialize the overlay with old parameters.
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, oldParams);
-
- // Show the overlay.
- reset(mWindowManager);
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
- BiometricOverlayConstants.REASON_ENROLL_ENROLLING,
- mUdfpsOverlayControllerCallback);
- mFgExecutor.runAllReady();
- verify(mWindowManager).addView(any(), any());
-
- // Update overlay parameters.
- reset(mWindowManager);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, newParams);
- mFgExecutor.runAllReady();
-
- // Ensure the overlay was recreated.
- verify(mWindowManager).removeView(any());
- verify(mWindowManager).addView(any(), any());
}
}
@@ -469,18 +489,18 @@
final int rotation = Surface.ROTATION_0;
// Initialize the overlay.
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
rotation));
// Show the overlay.
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mWindowManager).addView(any(), any());
// Update overlay with the same parameters.
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
rotation));
mFgExecutor.runAllReady();
@@ -522,13 +542,13 @@
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
// Show the overlay.
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
// Test ROTATION_0
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_0));
MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -545,7 +565,7 @@
// Test ROTATION_90
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_90));
event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -561,7 +581,7 @@
// Test ROTATION_270
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_270));
event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -577,7 +597,7 @@
// Test ROTATION_180
reset(mAlternateTouchProvider);
- mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+ mUdfpsController.updateOverlayParams(mOpticalProps,
new UdfpsOverlayParams(sensorBounds, displayWidth, displayHeight, scaleFactor,
Surface.ROTATION_180));
// ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -593,63 +613,108 @@
eq(expectedY), eq(expectedMinor), eq(expectedMajor));
}
+ private void runForAllUdfpsTypes(
+ ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) {
+ for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+ mUltrasonicProps)) {
+ mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
+ sensorPropsConsumer.accept(sensorProps);
+ }
+ }
+
@Test
- public void fingerDown() throws RemoteException {
+ public void fingerDown() {
+ runForAllUdfpsTypes(this::fingerDownForSensor);
+ }
+
+ private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
+ mKeyguardUpdateMonitor);
+
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN that the overlay is showing
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
- // WHEN ACTION_DOWN is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received
MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
mBiometricsExecutor.runAllReady();
downEvent.recycle();
- MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
- // FIX THIS TEST
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
mBiometricsExecutor.runAllReady();
moveEvent.recycle();
+
mFgExecutor.runAllReady();
+
// THEN FingerprintManager is notified about onPointerDown
verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
eq(0f));
verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
anyFloat(), anyFloat());
- verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
// AND display configuration begins
- verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ } else {
+ verify(mLatencyTracker, never()).onActionStart(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ verify(mUdfpsView, never()).configureDisplay(any());
+ }
verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
- // AND onDisplayConfigured notifies FingerprintManager about onUiReady
- mOnDisplayConfiguredCaptor.getValue().run();
- mBiometricsExecutor.runAllReady();
- InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
- inOrder.verify(mAlternateTouchProvider).onUiReady();
- inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+ mOnDisplayConfiguredCaptor.getValue().run();
+ mBiometricsExecutor.runAllReady();
+ InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+ inOrder.verify(mAlternateTouchProvider).onUiReady();
+ inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ } else {
+ verify(mAlternateTouchProvider, never()).onUiReady();
+ verify(mLatencyTracker, never()).onActionEnd(
+ eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+ }
}
@Test
- public void aodInterrupt() throws RemoteException {
+ public void aodInterrupt() {
+ runForAllUdfpsTypes(this::aodInterruptForSensor);
+ }
+
+ private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ mUdfpsController.cancelAodInterrupt();
+ reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
// GIVEN that the overlay is showing and screen is on and fp is running
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
mFgExecutor.runAllReady();
- // THEN display configuration begins
- // AND onDisplayConfigured notifies FingerprintManager about onUiReady
- verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
- mOnDisplayConfiguredCaptor.getValue().run();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN display configuration begins
+ // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+ verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ mOnDisplayConfiguredCaptor.getValue().run();
+ } else {
+ verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+ }
mBiometricsExecutor.runAllReady();
verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -659,54 +724,92 @@
}
@Test
- public void cancelAodInterrupt() throws RemoteException {
+ public void cancelAodInterrupt() {
+ runForAllUdfpsTypes(this::cancelAodInterruptForSensor);
+ }
+
+ private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
- // WHEN it is cancelled
- mUdfpsController.cancelAodInterrupt();
- // THEN the display is unconfigured
- verify(mUdfpsView).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ // WHEN it is cancelled
+ mUdfpsController.cancelAodInterrupt();
+ // THEN the display is unconfigured
+ verify(mUdfpsView).unconfigureDisplay();
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ // WHEN it is cancelled
+ mUdfpsController.cancelAodInterrupt();
+ // THEN the display configuration is unchanged.
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptTimeout() throws RemoteException {
+ public void aodInterruptTimeout() {
+ runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor);
+ }
+
+ private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the display is unconfigured
- verify(mUdfpsView).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN the display is unconfigured.
+ verify(mUdfpsView).unconfigureDisplay();
+ } else {
+ // THEN the display configuration is unchanged.
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
+ public void aodInterruptCancelTimeoutActionOnFingerUp() {
+ runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor);
+ }
+
+ private void aodInterruptCancelTimeoutActionOnFingerUpForSensor(
+ FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the ACTION_UP event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the ACTION_UP event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN ACTION_UP is received
verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -731,37 +834,54 @@
moveEvent.recycle();
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the finger up event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the display should be unconfigured once. If the timeout action is not
- // cancelled, the display would be unconfigured twice which would cause two
- // FP attempts.
- verify(mUdfpsView, times(1)).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ } else {
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+ public void aodInterruptCancelTimeoutActionOnAcquired() {
+ runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor);
+ }
+
+ private void aodInterruptCancelTimeoutActionOnAcquiredForSensor(
+ FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ reset(mUdfpsView);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// GIVEN AOD interrupt
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the acquired event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the acquired event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN acquired is received
- mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+ mOverlayController.onAcquired(sensorProps.sensorId,
BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
// Configure UdfpsView to accept the ACTION_DOWN event
@@ -781,29 +901,43 @@
moveEvent.recycle();
mFgExecutor.runAllReady();
- // Configure UdfpsView to accept the finger up event
- when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+ } else {
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ }
// WHEN it times out
mFgExecutor.advanceClockToNext();
mFgExecutor.runAllReady();
- // THEN the display should be unconfigured once. If the timeout action is not
- // cancelled, the display would be unconfigured twice which would cause two
- // FP attempts.
- verify(mUdfpsView, times(1)).unconfigureDisplay();
+ if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ } else {
+ verify(mUdfpsView, never()).unconfigureDisplay();
+ }
}
@Test
- public void aodInterruptScreenOff() throws RemoteException {
+ public void aodInterruptScreenOff() {
+ runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor);
+ }
+
+ private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps)
+ throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN screen off
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOff();
mFgExecutor.runAllReady();
// WHEN aod interrupt is received
- when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN display doesn't get configured because it's off
@@ -811,9 +945,16 @@
}
@Test
- public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+ public void aodInterrupt_fingerprintNotRunning() {
+ runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor);
+ }
+
+ private void aodInterrupt_fingerprintNotRunningForSensor(
+ FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+ reset(mUdfpsView);
+
// GIVEN showing overlay
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
@@ -835,7 +976,7 @@
// GIVEN that the overlay is showing and a11y touch exploration enabled
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
@@ -870,7 +1011,7 @@
// GIVEN that the overlay is showing and a11y touch exploration NOT enabled
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
- mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 8c3ae3d..68a5f47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -43,6 +43,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -85,6 +86,10 @@
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var receiverUiEventLogger: MediaTttReceiverUiEventLogger
+ private lateinit var fakeClock: FakeSystemClock
+ private lateinit var fakeExecutor: FakeExecutor
+ private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+ private lateinit var fakeWakeLock: WakeLockFake
@Before
fun setUp() {
@@ -99,15 +104,22 @@
)).thenReturn(applicationInfo)
context.setMockPackageManager(packageManager)
+ fakeClock = FakeSystemClock()
+ fakeExecutor = FakeExecutor(fakeClock)
+
uiEventLoggerFake = UiEventLoggerFake()
receiverUiEventLogger = MediaTttReceiverUiEventLogger(uiEventLoggerFake)
+ fakeWakeLock = WakeLockFake()
+ fakeWakeLockBuilder = WakeLockFake.Builder(context)
+ fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
controllerReceiver = MediaTttChipControllerReceiver(
commandQueue,
context,
logger,
windowManager,
- FakeExecutor(FakeSystemClock()),
+ fakeExecutor,
accessibilityManager,
configurationController,
powerManager,
@@ -115,6 +127,7 @@
mediaTttFlags,
receiverUiEventLogger,
viewUtil,
+ fakeWakeLockBuilder,
)
controllerReceiver.start()
@@ -141,6 +154,7 @@
mediaTttFlags,
receiverUiEventLogger,
viewUtil,
+ fakeWakeLockBuilder,
)
controllerReceiver.start()
@@ -200,6 +214,39 @@
}
@Test
+ fun commandQueueCallback_closeThenFar_wakeLockAcquiredThenReleased() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
+ @Test
+ fun commandQueueCallback_closeThenFar_wakeLockNeverAcquired() {
+ commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
+ StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_FAR_FROM_SENDER,
+ routeInfo,
+ null,
+ null
+ )
+
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
+ @Test
fun receivesNewStateFromCommandQueue_isLogged() {
commandQueueCallback.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index ad19bc2..4437394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -89,6 +90,8 @@
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var vibratorHelper: VibratorHelper
+ private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+ private lateinit var fakeWakeLock: WakeLockFake
private lateinit var chipbarCoordinator: ChipbarCoordinator
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
@@ -118,6 +121,10 @@
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
+ fakeWakeLock = WakeLockFake()
+ fakeWakeLockBuilder = WakeLockFake.Builder(context)
+ fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
uiEventLoggerFake = UiEventLoggerFake()
uiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
@@ -134,6 +141,7 @@
falsingCollector,
viewUtil,
vibratorHelper,
+ fakeWakeLockBuilder,
)
chipbarCoordinator.start()
@@ -472,6 +480,36 @@
}
@Test
+ fun commandQueueCallback_almostCloseThenFarFromReceiver_wakeLockAcquiredThenReleased() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
+ @Test
+ fun commandQueueCallback_FarFromReceiver_wakeLockNeverReleased() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_FAR_FROM_RECEIVER,
+ routeInfo,
+ null
+ )
+
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
+ @Test
fun commandQueueCallback_invalidStateParam_noChipShown() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(100, routeInfo, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index bd4b94e..52462c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -311,6 +311,37 @@
}
@Test
+ fun testCallbackCalledOnUserInfoChanged() {
+ tracker.initialize(0)
+ val callback = TestCallback()
+ tracker.addCallback(callback, executor)
+ val profileID = tracker.userId + 10
+
+ `when`(userManager.getProfiles(anyInt())).thenAnswer { invocation ->
+ val id = invocation.getArgument<Int>(0)
+ val info = UserInfo(id, "", UserInfo.FLAG_FULL)
+ val infoProfile = UserInfo(
+ id + 10,
+ "",
+ "",
+ UserInfo.FLAG_MANAGED_PROFILE,
+ UserManager.USER_TYPE_PROFILE_MANAGED
+ )
+ infoProfile.profileGroupId = id
+ listOf(info, infoProfile)
+ }
+
+ val intent = Intent(Intent.ACTION_USER_INFO_CHANGED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(profileID))
+
+ tracker.onReceive(context, intent)
+
+ assertThat(callback.calledOnUserChanged).isEqualTo(0)
+ assertThat(callback.calledOnProfilesChanged).isEqualTo(1)
+ assertThat(callback.lastUserProfiles.map { it.id }).containsExactly(0, profileID)
+ }
+
+ @Test
fun testCallbackRemoved() {
tracker.initialize(0)
val newID = 5
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 9a13e93..9b17cc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -20,6 +20,7 @@
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_TOO_DARK;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
@@ -54,6 +55,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
@@ -88,6 +90,7 @@
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
@@ -173,6 +176,8 @@
private FaceHelpMessageDeferral mFaceHelpMessageDeferral;
@Mock
private ScreenLifecycle mScreenLifecycle;
+ @Mock
+ private AuthController mAuthController;
@Captor
private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
@Captor
@@ -263,8 +268,9 @@
mWakeLockBuilder,
mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
- mUserManager, mExecutor, mExecutor, mFalsingManager, mLockPatternUtils,
- mScreenLifecycle, mKeyguardBypassController, mAccessibilityManager,
+ mUserManager, mExecutor, mExecutor, mFalsingManager,
+ mAuthController, mLockPatternUtils, mScreenLifecycle,
+ mKeyguardBypassController, mAccessibilityManager,
mFaceHelpMessageDeferral);
mController.init();
mController.setIndicationArea(mIndicationArea);
@@ -1371,6 +1377,110 @@
}
+ @Test
+ public void onBiometricError_faceLockedOutFirstTime_showsThePassedInMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "first lockout");
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
+ createController();
+ fingerprintUnlockIsPossible();
+ onFaceLockoutError("first lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutFirstTimeAndFpNotAllowed_showsDefaultFollowup() {
+ createController();
+ fingerprintUnlockIsNotPossible();
+ onFaceLockoutError("first lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutSecondTimeInSession_showsUnavailableMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_unlock_unavailable));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutSecondTimeButUdfpsActive_showsNoMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ when(mAuthController.isUdfpsFingerDown()).thenReturn(true);
+ onFaceLockoutError("second lockout");
+
+ verifyNoMoreInteractions(mRotateTextViewController);
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
+ createController();
+ fingerprintUnlockIsPossible();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ @Test
+ public void onBiometricError_faceLockedOutAgainAndFpNotAllowed_showsDefaultFollowup() {
+ createController();
+ fingerprintUnlockIsNotPossible();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_whenFaceLockoutReset_onLockOutError_showsPassedInMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(false);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE, "second lockout");
+ }
+
+ @Test
+ public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
+ createController();
+ onFaceLockoutError("first lockout");
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FACE);
+
+ onFaceLockoutError("second lockout");
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_unlock_unavailable));
+ }
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
@@ -1419,4 +1529,33 @@
anyObject(), anyBoolean());
}
}
+
+ private void verifyIndicationShown(int indicationType, String message) {
+ verify(mRotateTextViewController)
+ .updateIndication(eq(indicationType),
+ mKeyguardIndicationCaptor.capture(),
+ eq(true));
+ assertThat(mKeyguardIndicationCaptor.getValue().getMessage().toString())
+ .isEqualTo(message);
+ }
+
+ private void fingerprintUnlockIsNotPossible() {
+ setupFingerprintUnlockPossible(false);
+ }
+
+ private void fingerprintUnlockIsPossible() {
+ setupFingerprintUnlockPossible(true);
+ }
+
+ private void setupFingerprintUnlockPossible(boolean possible) {
+ when(mKeyguardUpdateMonitor
+ .getCachedIsUnlockWithFingerprintPossible(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(possible);
+ }
+
+ private void onFaceLockoutError(String errMsg) {
+ mKeyguardUpdateMonitorCallback.onBiometricError(FACE_ERROR_LOCKOUT_PERMANENT,
+ errMsg,
+ BiometricSourceType.FACE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index 086e5df..b80b825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -92,7 +92,7 @@
iconContainer.calculateIconXTranslations()
assertEquals(10f, iconState.xTranslation)
- assertFalse(iconContainer.hasOverflow())
+ assertFalse(iconContainer.areIconsOverflowing())
}
@Test
@@ -121,7 +121,7 @@
assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation)
- assertFalse(iconContainer.hasOverflow())
+ assertFalse(iconContainer.areIconsOverflowing())
}
@Test
@@ -150,7 +150,7 @@
assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
- assertTrue(iconContainer.hasOverflow())
+ assertTrue(iconContainer.areIconsOverflowing())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 91b5c35..9dea48e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -35,6 +35,8 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -53,6 +55,9 @@
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
+ private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+ private lateinit var fakeWakeLock: WakeLockFake
+
@Mock
private lateinit var logger: TemporaryViewLogger
@Mock
@@ -74,6 +79,10 @@
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
+ fakeWakeLock = WakeLockFake()
+ fakeWakeLockBuilder = WakeLockFake.Builder(context)
+ fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
underTest = TestController(
context,
logger,
@@ -82,7 +91,9 @@
accessibilityManager,
configurationController,
powerManager,
+ fakeWakeLockBuilder,
)
+ underTest.start()
}
@Test
@@ -112,25 +123,33 @@
}
@Test
- fun displayView_screenOff_screenWakes() {
- whenever(powerManager.isScreenOn).thenReturn(false)
-
+ fun displayView_screenOff_wakeLockAcquired() {
underTest.displayView(getState())
- verify(powerManager).wakeUp(any(), any(), any())
+ assertThat(fakeWakeLock.isHeld).isTrue()
}
@Test
- fun displayView_screenAlreadyOn_screenNotWoken() {
+ fun displayView_screenAlreadyOn_wakeLockNotAcquired() {
whenever(powerManager.isScreenOn).thenReturn(true)
underTest.displayView(getState())
- verify(powerManager, never()).wakeUp(any(), any(), any())
+ assertThat(fakeWakeLock.isHeld).isFalse()
}
@Test
- fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
+ fun displayView_screenOff_wakeLockCanBeReleasedAfterTimeOut() {
+ underTest.displayView(getState())
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
+ @Test
+ fun displayView_twice_viewNotAddedTwice() {
underTest.displayView(getState())
reset(windowManager)
@@ -269,6 +288,7 @@
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
powerManager: PowerManager,
+ wakeLockBuilder: WakeLock.Builder,
) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger>(
context,
logger,
@@ -278,13 +298,12 @@
configurationController,
powerManager,
R.layout.chipbar,
+ wakeLockBuilder,
) {
var mostRecentViewInfo: ViewInfo? = null
override val windowLayoutParams = commonWindowLayoutParams
- override fun start() {}
-
override fun updateView(newInfo: ViewInfo, currentView: ViewGroup) {
mostRecentViewInfo = newInfo
}
@@ -292,6 +311,8 @@
override fun getTouchableRegion(view: View, outRect: Rect) {
outRect.setEmpty()
}
+
+ override fun start() {}
}
inner class ViewInfo(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index f643973..8e37aa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -43,6 +43,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -69,6 +70,8 @@
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var vibratorHelper: VibratorHelper
+ private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+ private lateinit var fakeWakeLock: WakeLockFake
private lateinit var fakeClock: FakeSystemClock
private lateinit var fakeExecutor: FakeExecutor
private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -81,6 +84,10 @@
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
+ fakeWakeLock = WakeLockFake()
+ fakeWakeLockBuilder = WakeLockFake.Builder(context)
+ fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
uiEventLoggerFake = UiEventLoggerFake()
underTest =
@@ -96,6 +103,7 @@
falsingCollector,
viewUtil,
vibratorHelper,
+ fakeWakeLockBuilder,
)
underTest.start()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 574f70e..beedf9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
/** A fake implementation of [ChipbarCoordinator] for testing. */
class FakeChipbarCoordinator(
@@ -41,6 +42,7 @@
falsingCollector: FalsingCollector,
viewUtil: ViewUtil,
vibratorHelper: VibratorHelper,
+ wakeLockBuilder: WakeLock.Builder,
) :
ChipbarCoordinator(
context,
@@ -54,6 +56,7 @@
falsingCollector,
viewUtil,
vibratorHelper,
+ wakeLockBuilder,
) {
override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
// Just bypass the animation in tests
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
new file mode 100644
index 0000000..51afbcb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -0,0 +1,55 @@
+package com.android.systemui.user
+
+import android.app.Dialog
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class CreateUserActivityTest : SysuiTestCase() {
+ open class CreateUserActivityTestable :
+ CreateUserActivity(
+ /* userCreator = */ mock(),
+ /* editUserInfoController = */ mock {
+ val dialog: Dialog = mock()
+ whenever(
+ createDialog(
+ /* activity = */ nullable(),
+ /* activityStarter = */ nullable(),
+ /* oldUserIcon = */ nullable(),
+ /* defaultUserName = */ nullable(),
+ /* title = */ nullable(),
+ /* successCallback = */ nullable(),
+ /* cancelCallback = */ nullable()
+ )
+ )
+ .thenReturn(dialog)
+ },
+ /* activityManager = */ mock(),
+ /* activityStarter = */ mock(),
+ )
+
+ @get:Rule val activityRule = ActivityScenarioRule(CreateUserActivityTestable::class.java)
+
+ @Test
+ fun onBackPressed_finishActivity() {
+ activityRule.scenario.onActivity { activity ->
+ assertThat(activity.isFinishing).isFalse()
+
+ activity.onBackPressed()
+
+ assertThat(activity.isFinishing).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
index 525d837..7c7f0e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
@@ -145,6 +145,25 @@
assertThat(userInfos).isEqualTo(expectedUsers)
}
+ @Test
+ fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+ underTest = create(this)
+ var selectedUserInfo: UserInfo? = null
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 0,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id == 0)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 1,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id == 1)
+ }
+
private fun setUpUsers(
count: Int,
isLastGuestUser: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index fe01f84..6e109ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -42,7 +42,9 @@
@Before
public void setUp() {
- mInner = WakeLock.createPartialInner(mContext, WakeLockTest.class.getName());
+ mInner = WakeLock.createWakeLockInner(mContext,
+ WakeLockTest.class.getName(),
+ PowerManager.PARTIAL_WAKE_LOCK);
mWakeLock = WakeLock.wrap(mInner, 20000);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 9726bf8..a7eadba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -68,4 +68,8 @@
callbacks.forEach { it.onUserChanged(_userId, userContext) }
}
+
+ fun onProfileChanged() {
+ callbacks.forEach { it.onProfilesChanged(_userProfiles) }
+ }
}