Merge changes from topic "udfps-light"

* changes:
  Instrument UDFPS touch affordance CUJ.
  Split UdfpsController and backfill tests.
  Migrate small UDFPS classes to kotlin and backfill missing tests.
This commit is contained in:
Joe Bolinger
2021-12-28 17:31:51 +00:00
committed by Android (Google) Code Review
23 changed files with 1589 additions and 1170 deletions

View File

@@ -127,6 +127,11 @@ public class LatencyTracker {
*/
public static final int ACTION_SWITCH_DISPLAY_UNFOLD = 13;
/**
* Time it takes for a UDFPS sensor to appear ready after it is touched.
*/
public static final int ACTION_UDFPS_ILLUMINATE = 14;
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -141,7 +146,8 @@ public class LatencyTracker {
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
ACTION_LOCKSCREEN_UNLOCK,
ACTION_USER_SWITCH,
ACTION_SWITCH_DISPLAY_UNFOLD
ACTION_SWITCH_DISPLAY_UNFOLD,
ACTION_UDFPS_ILLUMINATE
};
/** @hide */
@@ -159,7 +165,8 @@ public class LatencyTracker {
ACTION_ROTATE_SCREEN_CAMERA_CHECK,
ACTION_LOCKSCREEN_UNLOCK,
ACTION_USER_SWITCH,
ACTION_SWITCH_DISPLAY_UNFOLD
ACTION_SWITCH_DISPLAY_UNFOLD,
ACTION_UDFPS_ILLUMINATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -179,7 +186,8 @@ public class LatencyTracker {
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD,
FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE
};
private static LatencyTracker sLatencyTracker;
@@ -267,6 +275,8 @@ public class LatencyTracker {
return "ACTION_USER_SWITCH";
case 14:
return "ACTION_SWITCH_DISPLAY_UNFOLD";
case 15:
return "ACTION_UDFPS_ILLUMINATE";
default:
throw new IllegalArgumentException("Invalid action");
}

View File

@@ -33,7 +33,7 @@ import android.widget.FrameLayout;
* - sends sensor rect updates to fingerprint drawable
* - optionally can override dozeTimeTick to adjust views for burn-in mitigation
*/
abstract class UdfpsAnimationView extends FrameLayout {
public abstract class UdfpsAnimationView extends FrameLayout {
// mAlpha takes into consideration the status bar expansion amount to fade out icon when
// the status bar is expanded
private int mAlpha;

View File

@@ -1,202 +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.annotation.NonNull;
import android.graphics.PointF;
import android.graphics.RectF;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.util.ViewController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Handles:
* 1. registering for listeners when its view is attached and unregistering on view detached
* 2. pausing udfps when fingerprintManager may still be running but we temporarily want to hide
* the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth)
* 3. sending events to its view including:
* - illumination events
* - sensor position changes
* - doze time event
*/
abstract class UdfpsAnimationViewController<T extends UdfpsAnimationView>
extends ViewController<T> implements Dumpable {
@NonNull final StatusBarStateController mStatusBarStateController;
@NonNull final PanelExpansionStateManager mPanelExpansionStateManager;
@NonNull final SystemUIDialogManager mDialogManager;
@NonNull final DumpManager mDumpManger;
boolean mNotificationShadeVisible;
protected UdfpsAnimationViewController(
T view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull PanelExpansionStateManager panelExpansionStateManager,
@NonNull SystemUIDialogManager dialogManager,
@NonNull DumpManager dumpManager) {
super(view);
mStatusBarStateController = statusBarStateController;
mPanelExpansionStateManager = panelExpansionStateManager;
mDialogManager = dialogManager;
mDumpManger = dumpManager;
}
abstract @NonNull String getTag();
@Override
protected void onViewAttached() {
mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener);
mDialogManager.registerListener(mDialogListener);
mDumpManger.registerDumpable(getDumpTag(), this);
}
@Override
protected void onViewDetached() {
mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener);
mDialogManager.unregisterListener(mDialogListener);
mDumpManger.unregisterDumpable(getDumpTag());
}
/**
* in some cases, onViewAttached is called for the newly added view using an instance of
* this controller before onViewDetached is called on the previous view, so we must have a
* unique dump tag per instance of this class
* @return a unique tag for this instance of this class
*/
private String getDumpTag() {
return getTag() + " (" + this + ")";
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("mNotificationShadeVisible=" + mNotificationShadeVisible);
pw.println("shouldPauseAuth()=" + shouldPauseAuth());
pw.println("isPauseAuth=" + mView.isPauseAuth());
}
/**
* Returns true if the fingerprint manager is running but we want to temporarily pause
* authentication.
*/
boolean shouldPauseAuth() {
return mNotificationShadeVisible
|| mDialogManager.shouldHideAffordance();
}
/**
* Send pause auth update to our view.
*/
void updatePauseAuth() {
if (mView.setPauseAuth(shouldPauseAuth())) {
mView.postInvalidate();
}
}
/**
* Send sensor position change to our view. This rect contains paddingX and paddingY.
*/
void onSensorRectUpdated(RectF sensorRect) {
mView.onSensorRectUpdated(sensorRect);
}
/**
* Send dozeTimeTick to view in case it wants to handle its burn-in offset.
*/
void dozeTimeTick() {
if (mView.dozeTimeTick()) {
mView.postInvalidate();
}
}
/**
* @return the amount of translation needed if the view currently requires the user to touch
* somewhere other than the exact center of the sensor. For example, this can happen
* during guided enrollment.
*/
PointF getTouchTranslation() {
return new PointF(0, 0);
}
/**
* X-Padding to add to left and right of the sensor rectangle area to increase the size of our
* window to draw within.
* @return
*/
int getPaddingX() {
return 0;
}
/**
* Y-Padding to add to top and bottom of the sensor rectangle area to increase the size of our
* window to draw within.
*/
int getPaddingY() {
return 0;
}
/**
* Udfps has started illuminating and the fingerprint manager is working on authenticating.
*/
void onIlluminationStarting() {
mView.onIlluminationStarting();
mView.postInvalidate();
}
/**
* Udfps has stopped illuminating and the fingerprint manager is no longer attempting to
* authenticate.
*/
void onIlluminationStopped() {
mView.onIlluminationStopped();
mView.postInvalidate();
}
/**
* Whether to listen for touches outside of the view.
*/
boolean listenForTouchesOutsideView() {
return false;
}
/**
* Called on touches outside of the view if listenForTouchesOutsideView returns true
*/
void onTouchOutsideView() { }
private final PanelExpansionListener mPanelExpansionListener = new PanelExpansionListener() {
@Override
public void onPanelExpansionChanged(
float fraction, boolean expanded, boolean tracking) {
// Notification shade can be expanded but not visible (fraction: 0.0), for example
// when a heads-up notification (HUN) is showing.
mNotificationShadeVisible = expanded && fraction > 0f;
mView.onExpansionChanged(fraction);
updatePauseAuth();
}
};
private final SystemUIDialogManager.Listener mDialogListener =
(shouldHide) -> updatePauseAuth();
}

View File

@@ -0,0 +1,170 @@
/*
* 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.graphics.PointF
import android.graphics.RectF
import com.android.systemui.Dumpable
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.util.ViewController
import java.io.FileDescriptor
import java.io.PrintWriter
/**
* Handles:
* 1. registering for listeners when its view is attached and unregistering on view detached
* 2. pausing udfps when fingerprintManager may still be running but we temporarily want to hide
* the affordance. this allows us to fade the view in and out nicely (see shouldPauseAuth)
* 3. sending events to its view including:
* - illumination events
* - sensor position changes
* - doze time event
*/
abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
view: T,
protected val statusBarStateController: StatusBarStateController,
protected val panelExpansionStateManager: PanelExpansionStateManager,
protected val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager
) : ViewController<T>(view), Dumpable {
protected abstract val tag: String
private val view: T
get() = mView!!
private val dialogListener = SystemUIDialogManager.Listener { updatePauseAuth() }
private val panelExpansionListener =
PanelExpansionListener { fraction, expanded, tracking ->
// Notification shade can be expanded but not visible (fraction: 0.0), for example
// when a heads-up notification (HUN) is showing.
notificationShadeVisible = expanded && fraction > 0f
view.onExpansionChanged(fraction)
updatePauseAuth()
}
/** If the notification shade is visible. */
var notificationShadeVisible: Boolean = false
/**
* The amount of translation needed if the view currently requires the user to touch
* somewhere other than the exact center of the sensor. For example, this can happen
* during guided enrollment.
*/
open val touchTranslation: PointF = PointF(0f, 0f)
/**
* X-Padding to add to left and right of the sensor rectangle area to increase the size of our
* window to draw within.
*/
open val paddingX: Int = 0
/**
* Y-Padding to add to top and bottom of the sensor rectangle area to increase the size of our
* window to draw within.
*/
open val paddingY: Int = 0
override fun onViewAttached() {
panelExpansionStateManager.addExpansionListener(panelExpansionListener)
dialogManager.registerListener(dialogListener)
dumpManager.registerDumpable(dumpTag, this)
}
override fun onViewDetached() {
panelExpansionStateManager.removeExpansionListener(panelExpansionListener)
dialogManager.unregisterListener(dialogListener)
dumpManager.unregisterDumpable(dumpTag)
}
/**
* in some cases, onViewAttached is called for the newly added view using an instance of
* this controller before onViewDetached is called on the previous view, so we must have a
* unique [dumpTag] per instance of this class.
*/
private val dumpTag = "$tag ($this)"
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
pw.println("mNotificationShadeVisible=$notificationShadeVisible")
pw.println("shouldPauseAuth()=" + shouldPauseAuth())
pw.println("isPauseAuth=" + view.isPauseAuth)
}
/**
* Returns true if the fingerprint manager is running, but we want to temporarily pause
* authentication.
*/
open fun shouldPauseAuth(): Boolean {
return notificationShadeVisible || dialogManager.shouldHideAffordance()
}
/**
* Send pause auth update to our view.
*/
fun updatePauseAuth() {
if (view.setPauseAuth(shouldPauseAuth())) {
view.postInvalidate()
}
}
/**
* Send sensor position change to our view. This rect contains paddingX and paddingY.
*/
fun onSensorRectUpdated(sensorRect: RectF) {
view.onSensorRectUpdated(sensorRect)
}
/**
* Send dozeTimeTick to view in case it wants to handle its burn-in offset.
*/
fun dozeTimeTick() {
if (view.dozeTimeTick()) {
view.postInvalidate()
}
}
/**
* Udfps has started illuminating and the fingerprint manager is working on authenticating.
*/
fun onIlluminationStarting() {
view.onIlluminationStarting()
view.postInvalidate()
}
/**
* Udfps has stopped illuminating and the fingerprint manager is no longer attempting to
* authenticate.
*/
fun onIlluminationStopped() {
view.onIlluminationStopped()
view.postInvalidate()
}
/**
* Whether to listen for touches outside of the view.
*/
open fun listenForTouchesOutsideView(): Boolean = false
/**
* Called on touches outside of the view if listenForTouchesOutsideView returns true
*/
open fun onTouchOutsideView() {}
}

View File

@@ -13,33 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
package com.android.systemui.biometrics;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import android.content.Context
import android.util.AttributeSet
/**
* Class that coordinates non-HBM animations during BiometricPrompt.
*
* Currently doesn't draw anything.
*
* Note that {@link AuthBiometricUdfpsView} also shows UDFPS animations. At some point we should
* Note that [AuthBiometricUdfpsView] also shows UDFPS animations. At some point we should
* de-dupe this if necessary.
*/
public class UdfpsBpView extends UdfpsAnimationView {
private UdfpsFpDrawable mFingerprintDrawable;
class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) {
public UdfpsBpView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// Drawable isn't ever added to the view, so we don't currently show anything
mFingerprintDrawable = new UdfpsFpDrawable(mContext);
}
// Drawable isn't ever added to the view, so we don't currently show anything
private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context)
@Override
UdfpsDrawable getDrawable() {
return mFingerprintDrawable;
}
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
}

View File

@@ -13,32 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
package com.android.systemui.biometrics;
import android.annotation.NonNull;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
/**
* Class that coordinates non-HBM animations for biometric prompt.
*/
class UdfpsBpViewController extends UdfpsAnimationViewController<UdfpsBpView> {
protected UdfpsBpViewController(
@NonNull UdfpsBpView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull PanelExpansionStateManager panelExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager) {
super(view, statusBarStateController, panelExpansionStateManager,
systemUIDialogManager, dumpManager);
}
@Override
@NonNull String getTag() {
return "UdfpsBpViewController";
}
class UdfpsBpViewController(
view: UdfpsBpView,
statusBarStateController: StatusBarStateController,
panelExpansionStateManager: PanelExpansionStateManager,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsBpView>(
view,
statusBarStateController,
panelExpansionStateManager,
systemUIDialogManager,
dumpManager
) {
override val tag = "UdfpsBpViewController"
}

View File

@@ -24,15 +24,11 @@ import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RectF;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
@@ -42,24 +38,21 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.Handler;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
@@ -95,7 +88,7 @@ import kotlin.Unit;
* controls/manages all UDFPS sensors. In other words, a single controller is registered with
* {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
* as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
* {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have
* {@link IUdfpsOverlayController#showUdfpsOverlay} should all have
* {@code sensorId} parameters.
*/
@SuppressWarnings("deprecation")
@@ -132,11 +125,11 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final SystemClock mSystemClock;
@NonNull private final UnlockedScreenOffAnimationController
mUnlockedScreenOffAnimationController;
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
// 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 final FingerprintSensorPropertiesInternal mSensorProps;
private final WindowManager.LayoutParams mCoreLayoutParams;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
@Nullable private VelocityTracker mVelocityTracker;
@@ -150,9 +143,8 @@ public class UdfpsController implements DozeReceiver {
// TODO: We should probably try to make touch/illumination things more of a FSM
private boolean mGoodCaptureReceived;
@Nullable private UdfpsView mView;
// The current request from FingerprintService. Null if no current request.
@Nullable ServerRequest mServerRequest;
@Nullable UdfpsControllerOverlay mOverlay;
// The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
// to turn off high brightness mode. To get around this limitation, the state of the AOD
@@ -164,7 +156,7 @@ public class UdfpsController implements DozeReceiver {
private Runnable mAodInterruptRunnable;
private boolean mOnFingerDown;
private boolean mAttemptedToDismissKeyguard;
private Set<Callback> mCallbacks = new HashSet<>();
private final Set<Callback> mCallbacks = new HashSet<>();
@VisibleForTesting
public static final VibrationAttributes VIBRATION_ATTRIBUTES =
@@ -193,67 +185,20 @@ public class UdfpsController implements DozeReceiver {
}
};
/**
* Keeps track of state within a single FingerprintService request. Note that this state
* persists across configuration changes, etc, since it is considered a single request.
*
* TODO: Perhaps we can move more global variables into here
*/
private static class ServerRequest {
// Reason the overlay has been requested. See IUdfpsOverlayController for definitions.
final int mRequestReason;
@NonNull final IUdfpsOverlayControllerCallback mCallback;
@Nullable final UdfpsEnrollHelper mEnrollHelper;
ServerRequest(int requestReason, @NonNull IUdfpsOverlayControllerCallback callback,
@Nullable UdfpsEnrollHelper enrollHelper) {
mRequestReason = requestReason;
mCallback = callback;
mEnrollHelper = enrollHelper;
}
void onEnrollmentProgress(int remaining) {
if (mEnrollHelper != null) {
mEnrollHelper.onEnrollmentProgress(remaining);
}
}
void onAcquiredGood() {
if (mEnrollHelper != null) {
mEnrollHelper.animateIfLastStep();
}
}
void onEnrollmentHelp() {
if (mEnrollHelper != null) {
mEnrollHelper.onEnrollmentHelp();
}
}
void onUserCanceled() {
try {
mCallback.onUserCanceled();
} catch (RemoteException e) {
Log.e(TAG, "Remote exception", e);
}
}
}
public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
@Override
public void showUdfpsOverlay(int sensorId, int reason,
@NonNull IUdfpsOverlayControllerCallback callback) {
mFgExecutor.execute(() -> {
final UdfpsEnrollHelper enrollHelper;
if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
|| reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) {
enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
} else {
enrollHelper = null;
}
mServerRequest = new ServerRequest(reason, callback, enrollHelper);
updateOverlay();
});
mFgExecutor.execute(
() -> UdfpsController.this.showUdfpsOverlay(new UdfpsControllerOverlay(
mContext, mFingerprintManager, mInflater, mWindowManager,
mAccessibilityManager, mStatusBarStateController,
mPanelExpansionStateManager, mKeyguardViewManager,
mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
mLockscreenShadeTransitionController, mConfigurationController,
mSystemClock, mKeyguardStateController,
mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider,
reason, callback, UdfpsController.this::onTouch)));
}
@Override
@@ -266,57 +211,55 @@ public class UdfpsController implements DozeReceiver {
+ "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true");
}
mServerRequest = null;
updateOverlay();
UdfpsController.this.hideUdfpsOverlay();
});
}
@Override
public void onAcquiredGood(int sensorId) {
mFgExecutor.execute(() -> {
if (mView == null) {
Log.e(TAG, "Null view when onAcquiredGood for sensorId: " + sensorId);
if (mOverlay == null) {
Log.e(TAG, "Null request when onAcquiredGood for sensorId: " + sensorId);
return;
}
mGoodCaptureReceived = true;
mView.stopIllumination();
if (mServerRequest != null) {
mServerRequest.onAcquiredGood();
} else {
Log.e(TAG, "Null serverRequest when onAcquiredGood");
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
view.stopIllumination();
}
mOverlay.onAcquiredGood();
});
}
@Override
public void onEnrollmentProgress(int sensorId, int remaining) {
mFgExecutor.execute(() -> {
if (mServerRequest == null) {
if (mOverlay == null) {
Log.e(TAG, "onEnrollProgress received but serverRequest is null");
return;
}
mServerRequest.onEnrollmentProgress(remaining);
mOverlay.onEnrollmentProgress(remaining);
});
}
@Override
public void onEnrollmentHelp(int sensorId) {
mFgExecutor.execute(() -> {
if (mServerRequest == null) {
if (mOverlay == null) {
Log.e(TAG, "onEnrollmentHelp received but serverRequest is null");
return;
}
mServerRequest.onEnrollmentHelp();
mOverlay.onEnrollmentHelp();
});
}
@Override
public void setDebugMessage(int sensorId, String message) {
mFgExecutor.execute(() -> {
if (mView == null) {
if (mOverlay == null || mOverlay.isHiding()) {
return;
}
mView.setDebugMessage(message);
mOverlay.getOverlayView().setDebugMessage(message);
});
}
}
@@ -341,14 +284,13 @@ public class UdfpsController implements DozeReceiver {
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mServerRequest != null
&& mServerRequest.mRequestReason != REASON_AUTH_KEYGUARD
if (mOverlay != null
&& mOverlay.getRequestReason() != REASON_AUTH_KEYGUARD
&& Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: "
+ mServerRequest.mRequestReason);
mServerRequest.onUserCanceled();
mServerRequest = null;
updateOverlay();
+ mOverlay.getRequestReason());
mOverlay.cancel();
hideUdfpsOverlay();
}
}
};
@@ -357,23 +299,12 @@ public class UdfpsController implements DozeReceiver {
* Forwards touches to the udfps controller / view
*/
public boolean onTouch(MotionEvent event) {
if (mView == null) {
if (mOverlay == null || mOverlay.isHiding()) {
return false;
}
return onTouch(mView, event, false);
return onTouch(mOverlay.getOverlayView(), event, false);
}
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) ->
onTouch(view, event, true);
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnHoverListener mOnHoverListener = (view, event) ->
onTouch(view, event, true);
private final AccessibilityManager.TouchExplorationStateChangeListener
mTouchExplorationStateChangeListener = enabled -> updateTouchListener();
/**
* @param x coordinate
* @param y coordinate
@@ -387,15 +318,15 @@ public class UdfpsController implements DozeReceiver {
return udfpsView.isWithinSensorArea(x, y);
}
if (mView == null || mView.getAnimationViewController() == null) {
if (mOverlay == null || mOverlay.getAnimationViewController() == null) {
return false;
}
return !mView.getAnimationViewController().shouldPauseAuth()
return !mOverlay.getAnimationViewController().shouldPauseAuth()
&& getSensorLocation().contains(x, y);
}
private boolean onTouch(View view, MotionEvent event, boolean fromUdfpsView) {
private boolean onTouch(@NonNull View view, @NonNull MotionEvent event, boolean fromUdfpsView) {
UdfpsView udfpsView = (UdfpsView) view;
final boolean isIlluminationRequested = udfpsView.isIlluminationRequested();
boolean handled = false;
@@ -419,6 +350,7 @@ public class UdfpsController implements DozeReceiver {
boolean withinSensorArea =
isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
if (withinSensorArea) {
mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
Log.v(TAG, "onTouch | action down");
// The pointer that causes ACTION_DOWN is always at index 0.
@@ -492,7 +424,7 @@ public class UdfpsController implements DozeReceiver {
}
} else {
Log.v(TAG, "onTouch | finger outside");
onFingerUp();
onFingerUp(udfpsView);
}
}
Trace.endSection();
@@ -509,7 +441,7 @@ public class UdfpsController implements DozeReceiver {
}
Log.v(TAG, "onTouch | finger up");
mAttemptedToDismissKeyguard = false;
onFingerUp();
onFingerUp(udfpsView);
mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
Trace.endSection();
break;
@@ -521,8 +453,8 @@ public class UdfpsController implements DozeReceiver {
}
private boolean shouldTryToDismissKeyguard() {
return mView.getAnimationViewController() != null
&& mView.getAnimationViewController() instanceof UdfpsKeyguardViewController
return mOverlay != null
&& mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController
&& mKeyguardStateController.canDismissLockScreen()
&& !mAttemptedToDismissKeyguard;
}
@@ -554,7 +486,8 @@ public class UdfpsController implements DozeReceiver {
@NonNull ConfigurationController configurationController,
@NonNull SystemClock systemClock,
@NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull SystemUIDialogManager dialogManager) {
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -582,6 +515,7 @@ public class UdfpsController implements DozeReceiver {
mConfigurationController = configurationController;
mSystemClock = systemClock;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLatencyTracker = latencyTracker;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -596,17 +530,6 @@ public class UdfpsController implements DozeReceiver {
return Unit.INSTANCE;
});
mCoreLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
0 /* flags set in computeLayoutParams() */,
PixelFormat.TRANSLUCENT);
mCoreLayoutParams.setTitle(TAG);
mCoreLayoutParams.setFitInsetsTypes(0);
mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mCoreLayoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
final IntentFilter filter = new IntentFilter();
@@ -644,8 +567,11 @@ public class UdfpsController implements DozeReceiver {
@Override
public void dozeTimeTick() {
if (mView != null) {
mView.dozeTimeTick();
if (mOverlay != null) {
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
view.dozeTimeTick();
}
}
}
@@ -664,233 +590,63 @@ public class UdfpsController implements DozeReceiver {
location.sensorLocationY + location.sensorRadius);
}
private void updateOverlay() {
mExecution.assertIsMainThread();
if (mServerRequest != null) {
showUdfpsOverlay(mServerRequest);
} else {
private void redrawOverlay() {
UdfpsControllerOverlay overlay = mOverlay;
if (overlay != null) {
hideUdfpsOverlay();
showUdfpsOverlay(overlay);
}
}
private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) {
if (!(animation instanceof UdfpsKeyguardViewController)) {
// always rotate view if we're not on the keyguard
return true;
}
// on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) {
return false;
}
return true;
}
private WindowManager.LayoutParams computeLayoutParams(
@Nullable UdfpsAnimationViewController animation) {
final int paddingX = animation != null ? animation.getPaddingX() : 0;
final int paddingY = animation != null ? animation.getPaddingY() : 0;
mCoreLayoutParams.flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
if (animation != null && animation.listenForTouchesOutsideView()) {
mCoreLayoutParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
}
// Default dimensions assume portrait mode.
final SensorLocationInternal location = mSensorProps.getLocation();
mCoreLayoutParams.x = location.sensorLocationX - location.sensorRadius - paddingX;
mCoreLayoutParams.y = location.sensorLocationY - location.sensorRadius - paddingY;
mCoreLayoutParams.height = 2 * location.sensorRadius + 2 * paddingX;
mCoreLayoutParams.width = 2 * location.sensorRadius + 2 * paddingY;
Point p = new Point();
// Gets the size based on the current rotation of the display.
mContext.getDisplay().getRealSize(p);
// Transform dimensions if the device is in landscape mode
switch (mContext.getDisplay().getRotation()) {
case Surface.ROTATION_90:
if (!shouldRotate(animation)) {
Log.v(TAG, "skip rotating udfps location ROTATION_90");
break;
} else {
Log.v(TAG, "rotate udfps location ROTATION_90");
}
mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius
- paddingX;
mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius
- paddingY;
break;
case Surface.ROTATION_270:
if (!shouldRotate(animation)) {
Log.v(TAG, "skip rotating udfps location ROTATION_270");
break;
} else {
Log.v(TAG, "rotate udfps location ROTATION_270");
}
mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius
- paddingX;
mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius
- paddingY;
break;
default:
// Do nothing to stay in portrait mode.
// Keyguard is always in portrait mode.
}
// avoid announcing window title
mCoreLayoutParams.accessibilityTitle = " ";
return mCoreLayoutParams;
}
private void onOrientationChanged() {
// When the configuration changes it's almost always necessary to destroy and re-create
// the overlay's window to pass it the new LayoutParams.
// Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
// of whether it is already hidden.
final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
hideUdfpsOverlay();
// If the overlay needs to be shown, this will re-create and show the overlay with the
// updated LayoutParams. Otherwise, the overlay will remain hidden.
updateOverlay();
redrawOverlay();
if (wasShowingAltAuth) {
mKeyguardViewManager.showGenericBouncer(true);
}
}
private void showUdfpsOverlay(@NonNull ServerRequest request) {
private void showUdfpsOverlay(@NonNull UdfpsControllerOverlay overlay) {
mExecution.assertIsMainThread();
final int reason = request.mRequestReason;
if (mView == null) {
try {
Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason);
mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
mOnFingerDown = false;
mView.setSensorProperties(mSensorProps);
mView.setHbmProvider(mHbmProvider);
UdfpsAnimationViewController<?> animation = inflateUdfpsAnimation(reason);
mAttemptedToDismissKeyguard = false;
if (animation != null) {
animation.init();
mView.setAnimationViewController(animation);
}
mOrientationListener.enable();
// This view overlaps the sensor area, so prevent it from being selectable
// during a11y.
if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
|| reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING
|| reason == BiometricOverlayConstants.REASON_AUTH_BP) {
mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
mWindowManager.addView(mView, computeLayoutParams(animation));
mAccessibilityManager.addTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
updateTouchListener();
} catch (RuntimeException e) {
Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
}
mOverlay = overlay;
if (overlay.show(this)) {
Log.v(TAG, "showUdfpsOverlay | adding window reason="
+ overlay.getRequestReason());
mOnFingerDown = false;
mAttemptedToDismissKeyguard = false;
mOrientationListener.enable();
} else {
Log.v(TAG, "showUdfpsOverlay | the overlay is already showing");
}
}
@Nullable
private UdfpsAnimationViewController<?> inflateUdfpsAnimation(int reason) {
switch (reason) {
case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR:
case BiometricOverlayConstants.REASON_ENROLL_ENROLLING:
UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate(
R.layout.udfps_enroll_view, null);
mView.addView(enrollView);
enrollView.updateSensorLocation(mSensorProps);
return new UdfpsEnrollViewController(
enrollView,
mServerRequest.mEnrollHelper,
mStatusBarStateController,
mPanelExpansionStateManager,
mDialogManager,
mDumpManager
);
case BiometricOverlayConstants.REASON_AUTH_KEYGUARD:
UdfpsKeyguardView keyguardView = (UdfpsKeyguardView)
mInflater.inflate(R.layout.udfps_keyguard_view, null);
mView.addView(keyguardView);
return new UdfpsKeyguardViewController(
keyguardView,
mStatusBarStateController,
mPanelExpansionStateManager,
mKeyguardViewManager,
mKeyguardUpdateMonitor,
mDumpManager,
mLockscreenShadeTransitionController,
mConfigurationController,
mSystemClock,
mKeyguardStateController,
mUnlockedScreenOffAnimationController,
mDialogManager,
this
);
case BiometricOverlayConstants.REASON_AUTH_BP:
// note: empty controller, currently shows no visual affordance
UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null);
mView.addView(bpView);
return new UdfpsBpViewController(
bpView,
mStatusBarStateController,
mPanelExpansionStateManager,
mDialogManager,
mDumpManager
);
case BiometricOverlayConstants.REASON_AUTH_OTHER:
case BiometricOverlayConstants.REASON_AUTH_SETTINGS:
UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView)
mInflater.inflate(R.layout.udfps_fpm_other_view, null);
mView.addView(authOtherView);
return new UdfpsFpmOtherViewController(
authOtherView,
mStatusBarStateController,
mPanelExpansionStateManager,
mDialogManager,
mDumpManager
);
default:
Log.e(TAG, "Animation for reason " + reason + " not supported yet");
return null;
}
}
private void hideUdfpsOverlay() {
mExecution.assertIsMainThread();
if (mView != null) {
Log.v(TAG, "hideUdfpsOverlay | removing window");
if (mOverlay != null) {
// Reset the controller back to its starting state.
onFingerUp();
boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView.setAnimationViewController(null);
if (wasShowingAltAuth) {
final UdfpsView oldView = mOverlay.getOverlayView();
if (oldView != null) {
onFingerUp(oldView);
}
final boolean removed = mOverlay.hide();
if (mKeyguardViewManager.isShowingAlternateAuth()) {
mKeyguardViewManager.resetAlternateAuth(true);
}
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
mView = null;
Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed);
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
mOverlay = null;
mOrientationListener.disable();
}
@@ -954,7 +710,9 @@ public class UdfpsController implements DozeReceiver {
* the user lifts their finger.
*/
void onCancelUdfps() {
onFingerUp();
if (mOverlay != null && mOverlay.getOverlayView() != null) {
onFingerUp(mOverlay.getOverlayView());
}
if (!mIsAodInterruptActive) {
return;
}
@@ -971,12 +729,12 @@ public class UdfpsController implements DozeReceiver {
private void onFingerDown(int x, int y, float minor, float major) {
mExecution.assertIsMainThread();
if (mView == null) {
Log.w(TAG, "Null view in onFingerDown");
if (mOverlay == null) {
Log.w(TAG, "Null request in onFingerDown");
return;
}
if (mView.getAnimationViewController() instanceof UdfpsKeyguardViewController
if (mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController
&& !mStatusBarStateController.isDozing()) {
mKeyguardBypassController.setUserHasDeviceEntryIntent(true);
}
@@ -991,25 +749,26 @@ public class UdfpsController implements DozeReceiver {
mOnFingerDown = true;
mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0);
mView.startIllumination(() -> {
mFingerprintManager.onUiReady(mSensorProps.sensorId);
Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
});
final UdfpsView view = mOverlay.getOverlayView();
if (view != null) {
Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0);
view.startIllumination(() -> {
mFingerprintManager.onUiReady(mSensorProps.sensorId);
mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
});
}
for (Callback cb : mCallbacks) {
cb.onFingerDown();
}
}
private void onFingerUp() {
private void onFingerUp(@NonNull UdfpsView view) {
mExecution.assertIsMainThread();
mActivePointerId = -1;
mGoodCaptureReceived = false;
if (mView == null) {
Log.w(TAG, "Null view in onFingerUp");
return;
}
if (mOnFingerDown) {
mFingerprintManager.onPointerUp(mSensorProps.sensorId);
for (Callback cb : mCallbacks) {
@@ -1017,22 +776,8 @@ public class UdfpsController implements DozeReceiver {
}
}
mOnFingerDown = false;
if (mView.isIlluminationRequested()) {
mView.stopIllumination();
}
}
private void updateTouchListener() {
if (mView == null) {
return;
}
if (mAccessibilityManager.isTouchExplorationEnabled()) {
mView.setOnHoverListener(mOnHoverListener);
mView.setOnTouchListener(null);
} else {
mView.setOnHoverListener(null);
mView.setOnTouchListener(mOnTouchListener);
if (view.isIlluminationRequested()) {
view.stopIllumination();
}
}

View File

@@ -0,0 +1,354 @@
/*
* 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.annotation.SuppressLint
import android.annotation.UiThread
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Point
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.os.RemoteException
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.LayoutRes
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
private const val TAG = "UdfpsControllerOverlay"
/**
* Keeps track of the overlay state and UI resources associated with a single FingerprintService
* request. This state can persist across configuration changes via the [show] and [hide]
* methods.
*/
@UiThread
class UdfpsControllerOverlay(
private val context: Context,
fingerprintManager: FingerprintManager,
private val inflater: LayoutInflater,
private val windowManager: WindowManager,
private val accessibilityManager: AccessibilityManager,
private val statusBarStateController: StatusBarStateController,
private val panelExpansionStateManager: PanelExpansionStateManager,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dialogManager: SystemUIDialogManager,
private val dumpManager: DumpManager,
private val transitionController: LockscreenShadeTransitionController,
private val configurationController: ConfigurationController,
private val systemClock: SystemClock,
private val keyguardStateController: KeyguardStateController,
private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
private val sensorProps: FingerprintSensorPropertiesInternal,
private var hbmProvider: UdfpsHbmProvider,
@ShowReason val requestReason: Int,
private val controllerCallback: IUdfpsOverlayControllerCallback,
private val onTouch: (View, MotionEvent, Boolean) -> Boolean
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
private set
private var overlayTouchListener: TouchExplorationStateChangeListener? = null
private val coreLayoutParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
0 /* flags set in computeLayoutParams() */,
PixelFormat.TRANSLUCENT
).apply {
title = TAG
fitInsetsTypes = 0
gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
}
/** A helper if the [requestReason] was due to enrollment. */
val enrollHelper: UdfpsEnrollHelper? = if (requestReason.isEnrollmentReason()) {
UdfpsEnrollHelper(context, fingerprintManager, requestReason)
} else {
null
}
/** If the overlay is currently showing. */
val isShowing: Boolean
get() = overlayView != null
/** Opposite of [isShowing]. */
val isHiding: Boolean
get() = overlayView == null
/** The animation controller if the overlay [isShowing]. */
val animationViewController: UdfpsAnimationViewController<*>?
get() = overlayView?.animationViewController
/** Show the overlay or return false and do nothing if it is already showing. */
@SuppressLint("ClickableViewAccessibility")
fun show(controller: UdfpsController): Boolean {
if (overlayView == null) {
try {
overlayView = (inflater.inflate(
R.layout.udfps_view, null, false
) as UdfpsView).apply {
sensorProperties = sensorProps
setHbmProvider(hbmProvider)
val animation = inflateUdfpsAnimation(this, controller)
if (animation != null) {
animation.init()
animationViewController = animation
}
// This view overlaps the sensor area
// prevent it from being selectable during a11y
if (requestReason.isImportantForAccessibility()) {
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
windowManager.addView(this,
coreLayoutParams.updateForLocation(sensorProps.location, animation))
overlayTouchListener = TouchExplorationStateChangeListener {
if (accessibilityManager.isTouchExplorationEnabled) {
setOnHoverListener { v, event -> onTouch(v, event, true) }
setOnTouchListener(null)
} else {
setOnHoverListener(null)
setOnTouchListener { v, event -> onTouch(v, event, true) }
}
}
accessibilityManager.addTouchExplorationStateChangeListener(
overlayTouchListener!!
)
overlayTouchListener?.onTouchExplorationStateChanged(true)
}
} catch (e: RuntimeException) {
Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
}
return true
}
Log.v(TAG, "showUdfpsOverlay | the overlay is already showing")
return false
}
private fun inflateUdfpsAnimation(
view: UdfpsView,
controller: UdfpsController
): UdfpsAnimationViewController<*>? {
return when (requestReason) {
REASON_ENROLL_FIND_SENSOR,
REASON_ENROLL_ENROLLING -> {
UdfpsEnrollViewController(
view.addUdfpsView(R.layout.udfps_enroll_view) {
updateSensorLocation(sensorProps)
},
enrollHelper ?: throw IllegalStateException("no enrollment helper"),
statusBarStateController,
panelExpansionStateManager,
dialogManager,
dumpManager
)
}
BiometricOverlayConstants.REASON_AUTH_KEYGUARD -> {
UdfpsKeyguardViewController(
view.addUdfpsView(R.layout.udfps_keyguard_view),
statusBarStateController,
panelExpansionStateManager,
statusBarKeyguardViewManager,
keyguardUpdateMonitor,
dumpManager,
transitionController,
configurationController,
systemClock,
keyguardStateController,
unlockedScreenOffAnimationController,
dialogManager,
controller
)
}
BiometricOverlayConstants.REASON_AUTH_BP -> {
// note: empty controller, currently shows no visual affordance
UdfpsBpViewController(
view.addUdfpsView(R.layout.udfps_bp_view),
statusBarStateController,
panelExpansionStateManager,
dialogManager,
dumpManager
)
}
BiometricOverlayConstants.REASON_AUTH_OTHER,
BiometricOverlayConstants.REASON_AUTH_SETTINGS -> {
UdfpsFpmOtherViewController(
view.addUdfpsView(R.layout.udfps_fpm_other_view),
statusBarStateController,
panelExpansionStateManager,
dialogManager,
dumpManager
)
}
else -> {
Log.e(TAG, "Animation for reason $requestReason not supported yet")
null
}
}
}
/** Hide the overlay or return false and do nothing if it is already hidden. */
fun hide(): Boolean {
val wasShowing = isShowing
overlayView?.apply {
if (isIlluminationRequested) {
stopIllumination()
}
windowManager.removeView(this)
setOnTouchListener(null)
setOnHoverListener(null)
animationViewController = null
overlayTouchListener?.let {
accessibilityManager.removeTouchExplorationStateChangeListener(it)
}
}
overlayView = null
overlayTouchListener = null
return wasShowing
}
fun onEnrollmentProgress(remaining: Int) {
enrollHelper?.onEnrollmentProgress(remaining)
}
fun onAcquiredGood() {
enrollHelper?.animateIfLastStep()
}
fun onEnrollmentHelp() {
enrollHelper?.onEnrollmentHelp()
}
/** Cancel this request. */
fun cancel() {
try {
controllerCallback.onUserCanceled()
} catch (e: RemoteException) {
Log.e(TAG, "Remote exception", e)
}
}
private fun WindowManager.LayoutParams.updateForLocation(
location: SensorLocationInternal,
animation: UdfpsAnimationViewController<*>?
): WindowManager.LayoutParams {
val paddingX = animation?.paddingX ?: 0
val paddingY = animation?.paddingY ?: 0
flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS
or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
if (animation != null && animation.listenForTouchesOutsideView()) {
flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
}
// Default dimensions assume portrait mode.
x = location.sensorLocationX - location.sensorRadius - paddingX
y = location.sensorLocationY - location.sensorRadius - paddingY
height = 2 * location.sensorRadius + 2 * paddingX
width = 2 * location.sensorRadius + 2 * paddingY
// Gets the size based on the current rotation of the display.
val p = Point()
context.display!!.getRealSize(p)
when (context.display!!.rotation) {
Surface.ROTATION_90 -> {
if (!shouldRotate(animation)) {
Log.v(TAG, "skip rotating udfps location ROTATION_90")
} else {
Log.v(TAG, "rotate udfps location ROTATION_90")
x = (location.sensorLocationY - location.sensorRadius - paddingX)
y = (p.y - location.sensorLocationX - location.sensorRadius - paddingY)
}
}
Surface.ROTATION_270 -> {
if (!shouldRotate(animation)) {
Log.v(TAG, "skip rotating udfps location ROTATION_270")
} else {
Log.v(TAG, "rotate udfps location ROTATION_270")
x = (p.x - location.sensorLocationY - location.sensorRadius - paddingX)
y = (location.sensorLocationX - location.sensorRadius - paddingY)
}
}
else -> {}
}
// avoid announcing window title
accessibilityTitle = " "
return this
}
private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean {
if (animation !is UdfpsKeyguardViewController) {
// always rotate view if we're not on the keyguard
return true
}
// on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
return !(keyguardUpdateMonitor.isGoingToSleep || !keyguardStateController.isOccluded)
}
private inline fun <reified T : View> UdfpsView.addUdfpsView(
@LayoutRes id: Int,
init: T.() -> Unit = {}
): T {
val subView = inflater.inflate(id, null) as T
addView(subView)
subView.init()
return subView
}
}
@ShowReason
private fun Int.isEnrollmentReason() =
this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING
@ShowReason
private fun Int.isImportantForAccessibility() =
this == REASON_ENROLL_FIND_SENSOR ||
this == REASON_ENROLL_ENROLLING ||
this == BiometricOverlayConstants.REASON_AUTH_BP

View File

@@ -1,113 +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.content.Context;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.util.PathParser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.R;
/**
* Abstract base class for drawable displayed when the finger is not touching the
* sensor area.
*/
public abstract class UdfpsDrawable extends Drawable {
static final float DEFAULT_STROKE_WIDTH = 3f;
@NonNull final Context mContext;
@NonNull final ShapeDrawable mFingerprintDrawable;
private final Paint mPaint;
private boolean mIlluminationShowing;
int mAlpha = 255; // 0 - 255
public UdfpsDrawable(@NonNull Context context) {
mContext = context;
final String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
mFingerprintDrawable = new ShapeDrawable(
new PathShape(PathParser.createPathFromPathData(fpPath), 72, 72));
mFingerprintDrawable.mutate();
mPaint = mFingerprintDrawable.getPaint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
setStrokeWidth(DEFAULT_STROKE_WIDTH);
}
void setStrokeWidth(float strokeWidth) {
mPaint.setStrokeWidth(strokeWidth);
invalidateSelf();
}
/**
* @param sensorRect the rect coordinates for the sensor area
*/
public void onSensorRectUpdated(@NonNull RectF sensorRect) {
final int margin = (int) sensorRect.height() / 8;
final Rect bounds = new Rect((int) sensorRect.left + margin,
(int) sensorRect.top + margin,
(int) sensorRect.right - margin,
(int) sensorRect.bottom - margin);
updateFingerprintIconBounds(bounds);
}
/**
* Bounds for the fingerprint icon
*/
protected void updateFingerprintIconBounds(@NonNull Rect bounds) {
mFingerprintDrawable.setBounds(bounds);
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
mFingerprintDrawable.setAlpha(mAlpha);
invalidateSelf();
}
boolean isIlluminationShowing() {
return mIlluminationShowing;
}
void setIlluminationShowing(boolean showing) {
if (mIlluminationShowing == showing) {
return;
}
mIlluminationShowing = showing;
invalidateSelf();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.content.Context
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.PathShape
import android.util.PathParser
import com.android.systemui.R
private const val DEFAULT_STROKE_WIDTH = 3f
/**
* Abstract base class for drawable displayed when the finger is not touching the
* sensor area.
*/
abstract class UdfpsDrawable(
protected val context: Context,
drawableFactory: (Context) -> ShapeDrawable
) : Drawable() {
constructor(context: Context) : this(context, defaultFactory)
/** Fingerprint affordance. */
val fingerprintDrawable: ShapeDrawable = drawableFactory(context)
private var _alpha: Int = 255 // 0 - 255
var strokeWidth: Float = fingerprintDrawable.paint.strokeWidth
set(value) {
field = value
fingerprintDrawable.paint.strokeWidth = value
invalidateSelf()
}
var isIlluminationShowing: Boolean = false
set(showing) {
if (field == showing) {
return
}
field = showing
invalidateSelf()
}
/** The [sensorRect] coordinates for the sensor area. */
open fun onSensorRectUpdated(sensorRect: RectF) {
val margin = sensorRect.height().toInt() / 8
val bounds = Rect(
sensorRect.left.toInt() + margin,
sensorRect.top.toInt() + margin,
sensorRect.right.toInt() - margin,
sensorRect.bottom.toInt() - margin
)
updateFingerprintIconBounds(bounds)
}
/** Bounds for the fingerprint icon. */
protected open fun updateFingerprintIconBounds(bounds: Rect) {
fingerprintDrawable.bounds = bounds
invalidateSelf()
}
override fun getAlpha(): Int = _alpha
override fun setAlpha(alpha: Int) {
_alpha = alpha
fingerprintDrawable.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = 0
}
private val defaultFactory = { context: Context ->
val fpPath = context.resources.getString(R.string.config_udfpsIcon)
val drawable = ShapeDrawable(
PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
)
drawable.mutate()
drawable.paint.style = Paint.Style.STROKE
drawable.paint.strokeCap = Paint.Cap.ROUND
drawable.paint.strokeWidth = DEFAULT_STROKE_WIDTH
drawable
}

View File

@@ -102,7 +102,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
mSensorOutlinePaint = new Paint(0 /* flags */);
mSensorOutlinePaint.setAntiAlias(true);
mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_moving_target_fill));
mSensorOutlinePaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
mSensorOutlinePaint.setStyle(Paint.Style.FILL);
mBlueFill = new Paint(0 /* flags */);
@@ -112,10 +112,10 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
mMovingTargetFpIcon = context.getResources()
.getDrawable(R.drawable.ic_kg_fingerprint, null);
mMovingTargetFpIcon.setTint(mContext.getColor(R.color.udfps_enroll_icon));
mMovingTargetFpIcon.setTint(context.getColor(R.color.udfps_enroll_icon));
mMovingTargetFpIcon.mutate();
mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
getFingerprintDrawable().setTint(context.getColor(R.color.udfps_enroll_icon));
mHintColorFaded = context.getColor(R.color.udfps_moving_target_fill);
mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
@@ -404,9 +404,9 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
if (mSensorRect != null) {
canvas.drawOval(mSensorRect, mSensorOutlinePaint);
}
mFingerprintDrawable.draw(canvas);
mFingerprintDrawable.setAlpha(mAlpha);
mSensorOutlinePaint.setAlpha(mAlpha);
getFingerprintDrawable().draw(canvas);
getFingerprintDrawable().setAlpha(getAlpha());
mSensorOutlinePaint.setAlpha(getAlpha());
}
// Draw the finger tip or edges hint.

View File

@@ -66,7 +66,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
}
@Override
@NonNull String getTag() {
@NonNull protected String getTag() {
return "UdfpsEnrollViewController";
}

View File

@@ -13,29 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
package com.android.systemui.biometrics;
import android.content.Context;
import android.graphics.Canvas;
import androidx.annotation.NonNull;
import android.content.Context
import android.graphics.Canvas
/**
* Draws udfps fingerprint if sensor isn't illuminating.
*/
public class UdfpsFpDrawable extends UdfpsDrawable {
UdfpsFpDrawable(@NonNull Context context) {
super(context);
}
@Override
public void draw(@NonNull Canvas canvas) {
if (isIlluminationShowing()) {
return;
class UdfpsFpDrawable(context: Context) : UdfpsDrawable(context) {
override fun draw(canvas: Canvas) {
if (isIlluminationShowing) {
return
}
mFingerprintDrawable.draw(canvas);
fingerprintDrawable.draw(canvas)
}
}

View File

@@ -1,49 +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.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.android.systemui.R;
/**
* View corresponding with udfps_fpm_other_view.xml
*/
public class UdfpsFpmOtherView extends UdfpsAnimationView {
private final UdfpsFpDrawable mFingerprintDrawable;
private ImageView mFingerprintView;
public UdfpsFpmOtherView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsFpDrawable(context);
}
@Override
protected void onFinishInflate() {
mFingerprintView = findViewById(R.id.udfps_fpm_other_fp_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
}
@Override
UdfpsDrawable getDrawable() {
return mFingerprintDrawable;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.content.Context
import android.util.AttributeSet
import android.widget.ImageView
import com.android.systemui.R
/**
* View corresponding with udfps_fpm_other_view.xml
*/
class UdfpsFpmOtherView(
context: Context,
attrs: AttributeSet?
) : UdfpsAnimationView(context, attrs) {
private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context)
private lateinit var fingerprintView: ImageView
override fun onFinishInflate() {
fingerprintView = findViewById(R.id.udfps_fpm_other_fp_view)!!
fingerprintView.setImageDrawable(fingerprintDrawable)
}
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
}

View File

@@ -13,15 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
package com.android.systemui.biometrics;
import android.annotation.NonNull;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
/**
* Class that coordinates non-HBM animations for non keyguard, enrollment or biometric prompt
@@ -29,19 +26,18 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManage
*
* Currently only shows the fp drawable.
*/
class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmOtherView> {
protected UdfpsFpmOtherViewController(
@NonNull UdfpsFpmOtherView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull PanelExpansionStateManager panelExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager) {
super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
dumpManager);
}
@Override
@NonNull String getTag() {
return "UdfpsFpmOtherViewController";
}
class UdfpsFpmOtherViewController(
view: UdfpsFpmOtherView,
statusBarStateController: StatusBarStateController,
panelExpansionStateManager: PanelExpansionStateManager,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmOtherView>(
view,
statusBarStateController,
panelExpansionStateManager,
systemUIDialogManager,
dumpManager
) {
override val tag = "UdfpsFpmOtherViewController"
}

View File

@@ -102,7 +102,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
@Override
@NonNull String getTag() {
@NonNull protected String getTag() {
return "UdfpsKeyguardViewController";
}
@@ -115,21 +115,21 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@Override
protected void onViewAttached() {
super.onViewAttached();
final float dozeAmount = mStatusBarStateController.getDozeAmount();
final float dozeAmount = getStatusBarStateController().getDozeAmount();
mLastDozeAmount = dozeAmount;
mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
mStatusBarStateController.addCallback(mStateListener);
getStatusBarStateController().addCallback(mStateListener);
mUdfpsRequested = false;
mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
mStatusBarState = mStatusBarStateController.getState();
mStatusBarState = getStatusBarStateController().getState();
mQsExpanded = mKeyguardViewManager.isQsExpanded();
mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
mConfigurationController.addCallback(mConfigurationListener);
mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener);
getPanelExpansionStateManager().addExpansionListener(mPanelExpansionListener);
updateAlpha();
updatePauseAuth();
@@ -144,11 +144,11 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mFaceDetectRunning = false;
mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
mStatusBarStateController.removeCallback(mStateListener);
getStatusBarStateController().removeCallback(mStateListener);
mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
mConfigurationController.removeCallback(mConfigurationListener);
mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener);
getPanelExpansionStateManager().removeExpansionListener(mPanelExpansionListener);
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
@@ -214,13 +214,13 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
return false;
}
if (mUdfpsRequested && !mNotificationShadeVisible
if (mUdfpsRequested && !getNotificationShadeVisible()
&& (!mIsBouncerVisible
|| mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)) {
return false;
}
if (mDialogManager.shouldHideAffordance()) {
if (getDialogManager().shouldHideAffordance()) {
return true;
}

View File

@@ -1,274 +0,0 @@
/*
* Copyright (C) 2020 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.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
import com.android.systemui.doze.DozeReceiver;
/**
* A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
* animations.
*/
public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator {
private static final String TAG = "UdfpsView";
private static final String SETTING_HBM_TYPE =
"com.android.systemui.biometrics.UdfpsSurfaceView.hbmType";
private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM;
private static final int DEBUG_TEXT_SIZE_PX = 32;
@NonNull private final RectF mSensorRect;
@NonNull private final Paint mDebugTextPaint;
private final float mSensorTouchAreaCoefficient;
private final int mOnIlluminatedDelayMs;
private final @HbmType int mHbmType;
// Only used for UdfpsHbmTypes.GLOBAL_HBM.
@Nullable private UdfpsSurfaceView mGhbmView;
// Can be different for enrollment, BiometricPrompt, Keyguard, etc.
@Nullable private UdfpsAnimationViewController mAnimationViewController;
// Used to obtain the sensor location.
@NonNull private FingerprintSensorPropertiesInternal mSensorProps;
@Nullable private UdfpsHbmProvider mHbmProvider;
@Nullable private String mDebugMessage;
private boolean mIlluminationRequested;
public UdfpsView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0,
0);
try {
if (!a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
throw new IllegalArgumentException(
"UdfpsView must contain sensorTouchAreaCoefficient");
}
mSensorTouchAreaCoefficient = a.getFloat(
R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f);
} finally {
a.recycle();
}
mSensorRect = new RectF();
mDebugTextPaint = new Paint();
mDebugTextPaint.setAntiAlias(true);
mDebugTextPaint.setColor(Color.BLUE);
mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
mOnIlluminatedDelayMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_udfps_illumination_transition_ms);
if (Build.IS_ENG || Build.IS_USERDEBUG) {
mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(),
SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT);
} else {
mHbmType = DEFAULT_HBM_TYPE;
}
}
// Don't propagate any touch events to the child views.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mAnimationViewController == null
|| !mAnimationViewController.shouldPauseAuth();
}
@Override
protected void onFinishInflate() {
if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) {
mGhbmView = findViewById(R.id.hbm_view);
}
}
void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
mSensorProps = properties;
}
@Override
public void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider) {
mHbmProvider = hbmProvider;
}
@Override
public void dozeTimeTick() {
if (mAnimationViewController != null) {
mAnimationViewController.dozeTimeTick();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int paddingX = mAnimationViewController == null ? 0
: mAnimationViewController.getPaddingX();
int paddingY = mAnimationViewController == null ? 0
: mAnimationViewController.getPaddingY();
final SensorLocationInternal location = mSensorProps.getLocation();
mSensorRect.set(
paddingX,
paddingY,
2 * location.sensorRadius + paddingX,
2 * location.sensorRadius + paddingY);
if (mAnimationViewController != null) {
mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect));
}
}
void onTouchOutsideView() {
if (mAnimationViewController != null) {
mAnimationViewController.onTouchOutsideView();
}
}
void setAnimationViewController(
@Nullable UdfpsAnimationViewController animationViewController) {
mAnimationViewController = animationViewController;
}
@Nullable UdfpsAnimationViewController getAnimationViewController() {
return mAnimationViewController;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Log.v(TAG, "onAttachedToWindow");
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
Log.v(TAG, "onDetachedFromWindow");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!mIlluminationRequested) {
if (!TextUtils.isEmpty(mDebugMessage)) {
canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
}
}
}
void setDebugMessage(String message) {
mDebugMessage = message;
postInvalidate();
}
boolean isWithinSensorArea(float x, float y) {
// The X and Y coordinates of the sensor's center.
final PointF translation = mAnimationViewController == null
? new PointF(0, 0)
: mAnimationViewController.getTouchTranslation();
final float cx = mSensorRect.centerX() + translation.x;
final float cy = mSensorRect.centerY() + translation.y;
// Radii along the X and Y axes.
final float rx = (mSensorRect.right - mSensorRect.left) / 2.0f;
final float ry = (mSensorRect.bottom - mSensorRect.top) / 2.0f;
return x > (cx - rx * mSensorTouchAreaCoefficient)
&& x < (cx + rx * mSensorTouchAreaCoefficient)
&& y > (cy - ry * mSensorTouchAreaCoefficient)
&& y < (cy + ry * mSensorTouchAreaCoefficient)
&& !mAnimationViewController.shouldPauseAuth();
}
boolean isIlluminationRequested() {
return mIlluminationRequested;
}
/**
* @param onIlluminatedRunnable Runs when the first illumination frame reaches the panel.
*/
@Override
public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
mIlluminationRequested = true;
if (mAnimationViewController != null) {
mAnimationViewController.onIlluminationStarting();
}
if (mGhbmView != null) {
mGhbmView.setGhbmIlluminationListener(this::doIlluminate);
mGhbmView.setVisibility(View.VISIBLE);
mGhbmView.startGhbmIllumination(onIlluminatedRunnable);
} else {
doIlluminate(null /* surface */, onIlluminatedRunnable);
}
}
private void doIlluminate(@Nullable Surface surface, @Nullable Runnable onIlluminatedRunnable) {
if (mGhbmView != null && surface == null) {
Log.e(TAG, "doIlluminate | surface must be non-null for GHBM");
}
if (mHbmProvider != null) {
mHbmProvider.enableHbm(mHbmType, surface, () -> {
if (mGhbmView != null) {
mGhbmView.drawIlluminationDot(mSensorRect);
}
if (onIlluminatedRunnable != null) {
// No framework API can reliably tell when a frame reaches the panel. A timeout
// is the safest solution.
postDelayed(onIlluminatedRunnable, mOnIlluminatedDelayMs);
} else {
Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null");
}
});
}
}
@Override
public void stopIllumination() {
mIlluminationRequested = false;
if (mAnimationViewController != null) {
mAnimationViewController.onIlluminationStopped();
}
if (mGhbmView != null) {
mGhbmView.setGhbmIlluminationListener(null);
mGhbmView.setVisibility(View.INVISIBLE);
}
if (mHbmProvider != null) {
mHbmProvider.disableHbm(null /* onHbmDisabled */);
}
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
import android.graphics.RectF
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Build
import android.os.UserHandle
import android.provider.Settings
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.Surface
import android.widget.FrameLayout
import com.android.systemui.R
import com.android.systemui.doze.DozeReceiver
import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType
private const val TAG = "UdfpsView"
private const val SETTING_HBM_TYPE = "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType"
@HbmType
private const val DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM
/**
* A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
* animations.
*/
class UdfpsView(
context: Context,
attrs: AttributeSet?
) : FrameLayout(context, attrs), DozeReceiver, UdfpsIlluminator {
private val sensorRect = RectF()
private var hbmProvider: UdfpsHbmProvider? = null
private val debugTextPaint = Paint().apply {
isAntiAlias = true
color = Color.BLUE
textSize = 32f
}
private val sensorTouchAreaCoefficient: Float =
context.theme.obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 0).use { a ->
require(a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
"UdfpsView must contain sensorTouchAreaCoefficient"
}
a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f)
}
private val onIlluminatedDelayMs = context.resources.getInteger(
com.android.internal.R.integer.config_udfps_illumination_transition_ms
).toLong()
@HbmType
private val hbmType = if (Build.IS_ENG || Build.IS_USERDEBUG) {
Settings.Secure.getIntForUser(
context.contentResolver,
SETTING_HBM_TYPE,
DEFAULT_HBM_TYPE,
UserHandle.USER_CURRENT
)
} else {
DEFAULT_HBM_TYPE
}
// Only used for UdfpsHbmTypes.GLOBAL_HBM.
private var ghbmView: UdfpsSurfaceView? = null
/** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
var animationViewController: UdfpsAnimationViewController<*>? = null
/** Properties used to obtain the sensor location. */
var sensorProperties: FingerprintSensorPropertiesInternal? = null
/** Debug message. */
var debugMessage: String? = null
set(value) {
field = value
postInvalidate()
}
/** When [startIllumination] has been called but not stopped via [stopIllumination]. */
var isIlluminationRequested: Boolean = false
private set
override fun setHbmProvider(provider: UdfpsHbmProvider?) {
hbmProvider = provider
}
// Don't propagate any touch events to the child views.
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return (animationViewController == null || !animationViewController!!.shouldPauseAuth())
}
override fun onFinishInflate() {
if (hbmType == UdfpsHbmTypes.GLOBAL_HBM) {
ghbmView = findViewById(R.id.hbm_view)
}
}
override fun dozeTimeTick() {
animationViewController?.dozeTimeTick()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val paddingX = animationViewController?.paddingX ?: 0
val paddingY = animationViewController?.paddingY ?: 0
val sensorRadius = sensorProperties?.location?.sensorRadius ?: 0
sensorRect.set(
paddingX.toFloat(),
paddingY.toFloat(),
(2 * sensorRadius + paddingX).toFloat(),
(2 * sensorRadius + paddingY).toFloat()
)
animationViewController?.onSensorRectUpdated(RectF(sensorRect))
}
fun onTouchOutsideView() {
animationViewController?.onTouchOutsideView()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
Log.v(TAG, "onAttachedToWindow")
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
Log.v(TAG, "onDetachedFromWindow")
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (!isIlluminationRequested) {
if (!debugMessage.isNullOrEmpty()) {
canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint)
}
}
}
fun isWithinSensorArea(x: Float, y: Float): Boolean {
// The X and Y coordinates of the sensor's center.
val translation = animationViewController?.touchTranslation ?: PointF(0f, 0f)
val cx = sensorRect.centerX() + translation.x
val cy = sensorRect.centerY() + translation.y
// Radii along the X and Y axes.
val rx = (sensorRect.right - sensorRect.left) / 2.0f
val ry = (sensorRect.bottom - sensorRect.top) / 2.0f
return x > cx - rx * sensorTouchAreaCoefficient &&
x < cx + rx * sensorTouchAreaCoefficient &&
y > cy - ry * sensorTouchAreaCoefficient &&
y < cy + ry * sensorTouchAreaCoefficient &&
!(animationViewController?.shouldPauseAuth() ?: false)
}
/**
* Start and run [onIlluminatedRunnable] when the first illumination frame reaches the panel.
*/
override fun startIllumination(onIlluminatedRunnable: Runnable?) {
isIlluminationRequested = true
animationViewController?.onIlluminationStarting()
val gView = ghbmView
if (gView != null) {
gView.setGhbmIlluminationListener(this::doIlluminate)
gView.visibility = VISIBLE
gView.startGhbmIllumination(onIlluminatedRunnable)
} else {
doIlluminate(null /* surface */, onIlluminatedRunnable)
}
}
private fun doIlluminate(surface: Surface?, onIlluminatedRunnable: Runnable?) {
if (ghbmView != null && surface == null) {
Log.e(TAG, "doIlluminate | surface must be non-null for GHBM")
}
hbmProvider?.enableHbm(hbmType, surface) {
ghbmView?.drawIlluminationDot(sensorRect)
if (onIlluminatedRunnable != null) {
// No framework API can reliably tell when a frame reaches the panel. A timeout
// is the safest solution.
postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs)
} else {
Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null")
}
}
}
override fun stopIllumination() {
isIlluminationRequested = false
animationViewController?.onIlluminationStopped()
ghbmView?.let { view ->
view.setGhbmIlluminationListener(null)
view.visibility = INVISIBLE
}
hbmProvider?.disableHbm(null /* onHbmDisabled */)
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
/** Creates properties from the sensor location with test values. */
fun SensorLocationInternal.asFingerprintSensorProperties(
sensorId: Int = 22,
@SensorProperties.Strength sensorStrength: Int = SensorProperties.STRENGTH_WEAK,
@FingerprintSensorProperties.SensorType sensorType: Int =
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
maxEnrollmentsPerUser: Int = 1,
info: List<ComponentInfoInternal> = listOf(ComponentInfoInternal("a", "b", "c", "d", "e")),
resetLockoutRequiresHardwareAuthToken: Boolean = false
) = FingerprintSensorPropertiesInternal(
sensorId,
sensorStrength,
maxEnrollmentsPerUser,
info,
sensorType,
resetLockoutRequiresHardwareAuthToken,
listOf(this)
)

View File

@@ -0,0 +1,288 @@
/*
* 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.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
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.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class UdfpsControllerOverlayTest : SysuiTestCase() {
@JvmField @Rule
var rule = MockitoJUnit.rule()
@Mock private lateinit var fingerprintManager: FingerprintManager
@Mock private lateinit var inflater: LayoutInflater
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var panelExpansionStateManager: PanelExpansionStateManager
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dialogManager: SystemUIDialogManager
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var transitionController: LockscreenShadeTransitionController
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock
private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
@Mock private lateinit var hbmProvider: UdfpsHbmProvider
@Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback
@Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
private val sensorProps = SensorLocationInternal("", 10, 100, 20)
.asFingerprintSensorProperties()
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
private lateinit var controllerOverlay: UdfpsControllerOverlay
@Before
fun setup() {
context.orCreateTestableResources.addOverride(R.integer.config_udfpsEnrollProgressBar, 20)
whenever(inflater.inflate(R.layout.udfps_view, null, false))
.thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_enroll_view, null))
.thenReturn(udfpsEnrollView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
whenever(inflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(mock(UdfpsKeyguardView::class.java))
whenever(inflater.inflate(R.layout.udfps_fpm_other_view, null))
.thenReturn(mock(UdfpsFpmOtherView::class.java))
whenever(udfpsEnrollView.context).thenReturn(context)
}
private fun withReason(@ShowReason reason: Int, block: () -> Unit) {
controllerOverlay = UdfpsControllerOverlay(
context, fingerprintManager, inflater, windowManager, accessibilityManager,
statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager,
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, sensorProps, hbmProvider, reason,
controllerCallback, onTouch)
block()
}
@Test
fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() }
@Test
fun showUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { showUdfpsOverlay() }
@Test
fun showUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { showUdfpsOverlay() }
@Test
fun showUdfpsOverlay_locate() = withReason(REASON_ENROLL_FIND_SENSOR) {
showUdfpsOverlay(isEnrollUseCase = true)
}
@Test
fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) {
showUdfpsOverlay(isEnrollUseCase = true)
}
@Test
fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() }
private fun showUdfpsOverlay(isEnrollUseCase: Boolean = false) {
val didShow = controllerOverlay.show(udfpsController)
verify(windowManager).addView(eq(controllerOverlay.overlayView), any())
verify(udfpsView).setHbmProvider(eq(hbmProvider))
verify(udfpsView).sensorProperties = eq(sensorProps)
verify(udfpsView).animationViewController = any()
verify(udfpsView).addView(any())
assertThat(didShow).isTrue()
assertThat(controllerOverlay.isShowing).isTrue()
assertThat(controllerOverlay.isHiding).isFalse()
assertThat(controllerOverlay.overlayView).isNotNull()
if (isEnrollUseCase) {
verify(udfpsEnrollView).updateSensorLocation(eq(sensorProps))
assertThat(controllerOverlay.enrollHelper).isNotNull()
} else {
assertThat(controllerOverlay.enrollHelper).isNull()
}
}
@Test
fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() }
@Test
fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() }
@Test
fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() }
@Test
fun hideUdfpsOverlay_locate() = withReason(REASON_ENROLL_FIND_SENSOR) { hideUdfpsOverlay() }
@Test
fun hideUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) { hideUdfpsOverlay() }
@Test
fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() }
private fun hideUdfpsOverlay() {
val didShow = controllerOverlay.show(udfpsController)
val view = controllerOverlay.overlayView
val didHide = controllerOverlay.hide()
verify(windowManager).removeView(eq(view))
assertThat(didShow).isTrue()
assertThat(didHide).isTrue()
assertThat(controllerOverlay.overlayView).isNull()
assertThat(controllerOverlay.animationViewController).isNull()
assertThat(controllerOverlay.isShowing).isFalse()
assertThat(controllerOverlay.isHiding).isTrue()
}
@Test
fun canNotHide() = withReason(REASON_AUTH_BP) {
assertThat(controllerOverlay.hide()).isFalse()
}
@Test
fun canNotReshow() = withReason(REASON_AUTH_BP) {
assertThat(controllerOverlay.show(udfpsController)).isTrue()
assertThat(controllerOverlay.show(udfpsController)).isFalse()
}
@Test
fun forwardEnrollProgressEvents() = withReason(REASON_ENROLL_ENROLLING) {
controllerOverlay.show(udfpsController)
with(EnrollListener(controllerOverlay)) {
controllerOverlay.onEnrollmentProgress(/* remaining */20)
controllerOverlay.onAcquiredGood()
assertThat(progress).isTrue()
assertThat(help).isFalse()
assertThat(acquired).isFalse()
}
}
@Test
fun forwardEnrollHelpEvents() = withReason(REASON_ENROLL_ENROLLING) {
controllerOverlay.show(udfpsController)
with(EnrollListener(controllerOverlay)) {
controllerOverlay.onEnrollmentHelp()
assertThat(progress).isFalse()
assertThat(help).isTrue()
assertThat(acquired).isFalse()
}
}
@Test
fun forwardEnrollAcquiredEvents() = withReason(REASON_ENROLL_ENROLLING) {
controllerOverlay.show(udfpsController)
with(EnrollListener(controllerOverlay)) {
controllerOverlay.onEnrollmentProgress(/* remaining */ 1)
controllerOverlay.onAcquiredGood()
assertThat(progress).isTrue()
assertThat(help).isFalse()
assertThat(acquired).isTrue()
}
}
@Test
fun cancels() = withReason(REASON_AUTH_BP) {
controllerOverlay.cancel()
verify(controllerCallback).onUserCanceled()
}
@Test
fun stopIlluminatingOnHide() = withReason(REASON_AUTH_BP) {
whenever(udfpsView.isIlluminationRequested).thenReturn(true)
controllerOverlay.show(udfpsController)
controllerOverlay.hide()
verify(udfpsView).stopIllumination()
}
}
private class EnrollListener(
overlay: UdfpsControllerOverlay,
var progress: Boolean = false,
var help: Boolean = false,
var acquired: Boolean = false
) : UdfpsEnrollHelper.Listener {
init {
overlay.enrollHelper!!.setListener(this)
}
override fun onEnrollmentProgress(remaining: Int, totalSteps: Int) {
progress = true
}
override fun onEnrollmentHelp(remaining: Int, totalSteps: Int) {
help = true
}
override fun onLastStepAcquired() {
acquired = true
}
}

View File

@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -53,6 +54,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -81,6 +83,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -154,7 +157,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private SystemClock mSystemClock;
@Mock
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock
private LatencyTracker mLatencyTracker;
private FakeExecutor mFgExecutor;
// Stuff for configuring mocks
@@ -163,9 +167,13 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private UdfpsEnrollView mEnrollView;
@Mock
private UdfpsKeyguardView mKeyguardView;
private UdfpsBpView mBpView;
@Mock
private UdfpsKeyguardViewController mUdfpsKeyguardViewController;
private UdfpsFpmOtherView mFpmOtherView;
@Mock
private UdfpsKeyguardView mKeyguardView;
private UdfpsAnimationViewController mUdfpsKeyguardViewController =
mock(UdfpsKeyguardViewController.class);
@Mock
private TypedArray mBrightnessValues;
@Mock
@@ -192,6 +200,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
.thenReturn(mEnrollView); // for showOverlay REASON_ENROLL_ENROLLING
when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
when(mLayoutInflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mBpView);
when(mLayoutInflater.inflate(R.layout.udfps_fpm_other_view, null))
.thenReturn(mFpmOtherView);
when(mEnrollView.getContext()).thenReturn(mContext);
when(mKeyguardStateController.isOccluded()).thenReturn(false);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
@@ -239,7 +251,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mConfigurationController,
mSystemClock,
mUnlockedScreenOffAnimationController,
mSystemUIDialogManager);
mSystemUIDialogManager,
mLatencyTracker);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -340,7 +353,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
when(mUdfpsView.getAnimationViewController()).thenReturn(
mock(UdfpsEnrollViewController.class));
(UdfpsAnimationViewController) mock(UdfpsEnrollViewController.class));
// GIVEN that the overlay is showing
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
@@ -405,83 +418,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
}
@Test
public void showUdfpsOverlay_addsViewToWindow_bp() throws RemoteException {
showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_BP);
}
@Test
public void showUdfpsOverlay_addsViewToWindow_keyguard() throws RemoteException {
showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
}
@Test
public void showUdfpsOverlay_addsViewToWindow_settings() throws RemoteException {
showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_SETTINGS);
}
@Test
public void showUdfpsOverlay_addsViewToWindow_enroll_locate() throws RemoteException {
showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR);
}
@Test
public void showUdfpsOverlay_addsViewToWindow_enroll() throws RemoteException {
showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_ENROLL_ENROLLING);
}
@Test
public void showUdfpsOverlay_addsViewToWindow_other() throws RemoteException {
showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_OTHER);
}
private void showUdfpsOverlay_addsViewToWindow(
@BiometricOverlayConstants.ShowReason int reason) throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, reason,
mUdfpsOverlayControllerCallback);
mFgExecutor.runAllReady();
verify(mWindowManager).addView(eq(mUdfpsView), any());
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow_bp() throws RemoteException {
hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_BP);
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow_keyguard() throws RemoteException {
hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow_settings() throws RemoteException {
hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_SETTINGS);
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow_enroll_locate() throws RemoteException {
hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR);
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow_enroll() throws RemoteException {
hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_ENROLL_ENROLLING);
}
@Test
public void hideUdfpsOverlay_removesViewFromWindow_other() throws RemoteException {
hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_OTHER);
}
private void hideUdfpsOverlay_removesViewFromWindow(
@BiometricOverlayConstants.ShowReason int reason) throws RemoteException {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
mFgExecutor.runAllReady();
verify(mWindowManager).removeView(eq(mUdfpsView));
}
@Test
public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
// GIVEN overlay was showing and the udfps bouncer is showing
@@ -532,11 +468,15 @@ public class UdfpsControllerTest extends SysuiTestCase {
// THEN FingerprintManager is notified about onPointerDown
verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
eq(0), eq(0f), eq(0f));
verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
// AND illumination begins
verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
// AND onIlluminatedRunnable notifies FingerprintManager about onUiReady
mOnIlluminatedRunnableCaptor.getValue().run();
verify(mFingerprintManager).onUiReady(eq(mUdfpsController.mSensorProps.sensorId));
InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker);
inOrder.verify(mFingerprintManager).onUiReady(eq(mUdfpsController.mSensorProps.sensorId));
inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
}
@Test

View File

@@ -0,0 +1,172 @@
/*
* 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.graphics.PointF
import android.graphics.RectF
import android.hardware.biometrics.SensorLocationInternal
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.LayoutInflater
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.nullable
import org.mockito.Mockito.never
import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val DISPLAY_ID = "" // default display id
private const val SENSOR_X = 50
private const val SENSOR_Y = 250
private const val SENSOR_RADIUS = 10
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class UdfpsViewTest : SysuiTestCase() {
@JvmField @Rule
var rule = MockitoJUnit.rule()
@Mock
lateinit var hbmProvider: UdfpsHbmProvider
@Mock
lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView>
private lateinit var view: UdfpsView
@Before
fun setup() {
context.setTheme(R.style.Theme_AppCompat)
context.orCreateTestableResources.addOverride(
com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0)
view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
view.animationViewController = animationViewController
view.sensorProperties =
SensorLocationInternal(DISPLAY_ID, SENSOR_X, SENSOR_Y, SENSOR_RADIUS)
.asFingerprintSensorProperties()
view.setHbmProvider(hbmProvider)
ViewUtils.attachView(view)
}
@After
fun cleanup() {
ViewUtils.detachView(view)
}
@Test
fun forwardsEvents() {
view.dozeTimeTick()
verify(animationViewController).dozeTimeTick()
view.onTouchOutsideView()
verify(animationViewController).onTouchOutsideView()
}
@Test
fun layoutSizeFitsSensor() {
val params = withArgCaptor<RectF> {
verify(animationViewController).onSensorRectUpdated(capture())
}
assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS)
assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS)
}
@Test
fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true)
@Test
fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false)
private fun isWithinSensorArea(paused: Boolean) {
whenever(animationViewController.shouldPauseAuth()).thenReturn(paused)
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
val end = (SENSOR_RADIUS * 2) - 1
for (x in 1 until end) {
for (y in 1 until end) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused)
}
}
}
@Test
fun isWithinSensorAreaWhenTranslated() {
val offset = PointF(100f, 200f)
whenever(animationViewController.touchTranslation).thenReturn(offset)
val end = (SENSOR_RADIUS * 2) - 1
for (x in 0 until offset.x.toInt() step 2) {
for (y in 0 until offset.y.toInt() step 2) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse()
}
}
for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) {
for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue()
}
}
}
@Test
fun isNotWithinSensorArea() {
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())).isFalse()
assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
}
@Test
fun startAndStopIllumination() {
val onDone: Runnable = mock()
view.startIllumination(onDone)
val illuminator = withArgCaptor<Runnable> {
verify(hbmProvider).enableHbm(anyInt(), nullable(Surface::class.java), capture())
}
assertThat(view.isIlluminationRequested).isTrue()
verify(animationViewController).onIlluminationStarting()
verify(animationViewController, never()).onIlluminationStopped()
verify(onDone, never()).run()
// fake illumination event
illuminator.run()
waitForLooper()
verify(onDone).run()
verify(hbmProvider, never()).disableHbm(any())
view.stopIllumination()
assertThat(view.isIlluminationRequested).isFalse()
verify(animationViewController).onIlluminationStopped()
verify(hbmProvider).disableHbm(nullable(Runnable::class.java))
}
private fun waitForLooper() = TestableLooper.get(this).processAllMessages()
}