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:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -66,7 +66,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull String getTag() {
|
||||
@NonNull protected String getTag() {
|
||||
return "UdfpsEnrollViewController";
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 */)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user