Migrate small UDFPS classes to kotlin and backfill missing tests.

Bug: 205875955
Test: atest UdfpsViewTest UdfpsControllerTest
Test: manual (enroll manually and BP test app)
Change-Id: Ia09f18bffe943ecdf7a73c71faae6f2e2d40e359
This commit is contained in:
Joe Bolinger
2021-11-29 11:58:48 -08:00
parent 5d8375c44d
commit 05c83b715a
19 changed files with 829 additions and 738 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,113 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics;
import android.content.Context;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.util.PathParser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.R;
/**
* Abstract base class for drawable displayed when the finger is not touching the
* sensor area.
*/
public abstract class UdfpsDrawable extends Drawable {
static final float DEFAULT_STROKE_WIDTH = 3f;
@NonNull final Context mContext;
@NonNull final ShapeDrawable mFingerprintDrawable;
private final Paint mPaint;
private boolean mIlluminationShowing;
int mAlpha = 255; // 0 - 255
public UdfpsDrawable(@NonNull Context context) {
mContext = context;
final String fpPath = context.getResources().getString(R.string.config_udfpsIcon);
mFingerprintDrawable = new ShapeDrawable(
new PathShape(PathParser.createPathFromPathData(fpPath), 72, 72));
mFingerprintDrawable.mutate();
mPaint = mFingerprintDrawable.getPaint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
setStrokeWidth(DEFAULT_STROKE_WIDTH);
}
void setStrokeWidth(float strokeWidth) {
mPaint.setStrokeWidth(strokeWidth);
invalidateSelf();
}
/**
* @param sensorRect the rect coordinates for the sensor area
*/
public void onSensorRectUpdated(@NonNull RectF sensorRect) {
final int margin = (int) sensorRect.height() / 8;
final Rect bounds = new Rect((int) sensorRect.left + margin,
(int) sensorRect.top + margin,
(int) sensorRect.right - margin,
(int) sensorRect.bottom - margin);
updateFingerprintIconBounds(bounds);
}
/**
* Bounds for the fingerprint icon
*/
protected void updateFingerprintIconBounds(@NonNull Rect bounds) {
mFingerprintDrawable.setBounds(bounds);
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
mFingerprintDrawable.setAlpha(mAlpha);
invalidateSelf();
}
boolean isIlluminationShowing() {
return mIlluminationShowing;
}
void setIlluminationShowing(boolean showing) {
if (mIlluminationShowing == showing) {
return;
}
mIlluminationShowing = showing;
invalidateSelf();
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
import android.content.Context
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.PathShape
import android.util.PathParser
import com.android.systemui.R
private const val DEFAULT_STROKE_WIDTH = 3f
/**
* Abstract base class for drawable displayed when the finger is not touching the
* sensor area.
*/
abstract class UdfpsDrawable(
protected val context: Context,
drawableFactory: (Context) -> ShapeDrawable
) : Drawable() {
constructor(context: Context) : this(context, defaultFactory)
/** Fingerprint affordance. */
val fingerprintDrawable: ShapeDrawable = drawableFactory(context)
private var _alpha: Int = 255 // 0 - 255
var strokeWidth: Float = fingerprintDrawable.paint.strokeWidth
set(value) {
field = value
fingerprintDrawable.paint.strokeWidth = value
invalidateSelf()
}
var isIlluminationShowing: Boolean = false
set(showing) {
if (field == showing) {
return
}
field = showing
invalidateSelf()
}
/** The [sensorRect] coordinates for the sensor area. */
open fun onSensorRectUpdated(sensorRect: RectF) {
val margin = sensorRect.height().toInt() / 8
val bounds = Rect(
sensorRect.left.toInt() + margin,
sensorRect.top.toInt() + margin,
sensorRect.right.toInt() - margin,
sensorRect.bottom.toInt() - margin
)
updateFingerprintIconBounds(bounds)
}
/** Bounds for the fingerprint icon. */
protected open fun updateFingerprintIconBounds(bounds: Rect) {
fingerprintDrawable.bounds = bounds
invalidateSelf()
}
override fun getAlpha(): Int = _alpha
override fun setAlpha(alpha: Int) {
_alpha = alpha
fingerprintDrawable.alpha = alpha
invalidateSelf()
}
override fun setColorFilter(colorFilter: ColorFilter?) {}
override fun getOpacity(): Int = 0
}
private val defaultFactory = { context: Context ->
val fpPath = context.resources.getString(R.string.config_udfpsIcon)
val drawable = ShapeDrawable(
PathShape(PathParser.createPathFromPathData(fpPath), 72f, 72f)
)
drawable.mutate()
drawable.paint.style = Paint.Style.STROKE
drawable.paint.strokeCap = Paint.Cap.ROUND
drawable.paint.strokeWidth = DEFAULT_STROKE_WIDTH
drawable
}

View File

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

View File

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

View File

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

View File

@@ -1,49 +0,0 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.android.systemui.R;
/**
* View corresponding with udfps_fpm_other_view.xml
*/
public class UdfpsFpmOtherView extends UdfpsAnimationView {
private final UdfpsFpDrawable mFingerprintDrawable;
private ImageView mFingerprintView;
public UdfpsFpmOtherView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsFpDrawable(context);
}
@Override
protected void onFinishInflate() {
mFingerprintView = findViewById(R.id.udfps_fpm_other_fp_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
}
@Override
UdfpsDrawable getDrawable() {
return mFingerprintDrawable;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageView
import com.android.systemui.R
/**
* View corresponding with udfps_fpm_other_view.xml
*/
class UdfpsFpmOtherView(
context: Context,
attrs: AttributeSet?
) : UdfpsAnimationView(context, attrs) {
private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context)
private lateinit var fingerprintView: ImageView
override fun onFinishInflate() {
fingerprintView = findViewById(R.id.udfps_fpm_other_fp_view)!!
fingerprintView.setImageDrawable(fingerprintDrawable)
}
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
}

View File

@@ -13,15 +13,13 @@
* 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 +27,18 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManage
*
* Currently only shows the fp drawable.
*/
class UdfpsFpmOtherViewController extends UdfpsAnimationViewController<UdfpsFpmOtherView> {
protected UdfpsFpmOtherViewController(
@NonNull UdfpsFpmOtherView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull PanelExpansionStateManager panelExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager) {
super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
dumpManager);
}
@Override
@NonNull String getTag() {
return "UdfpsFpmOtherViewController";
}
class UdfpsFpmOtherViewController(
view: UdfpsFpmOtherView,
statusBarStateController: StatusBarStateController,
panelExpansionStateManager: PanelExpansionStateManager,
systemUIDialogManager: SystemUIDialogManager,
dumpManager: DumpManager
) : UdfpsAnimationViewController<UdfpsFpmOtherView>(
view,
statusBarStateController,
panelExpansionStateManager,
systemUIDialogManager,
dumpManager
) {
override val tag = "UdfpsFpmOtherViewController"
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
/** Creates properties from the sensor location with test values. */
fun SensorLocationInternal.asFingerprintSensorProperties(
sensorId: Int = 22,
@SensorProperties.Strength sensorStrength: Int = SensorProperties.STRENGTH_WEAK,
@FingerprintSensorProperties.SensorType sensorType: Int =
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
maxEnrollmentsPerUser: Int = 1,
info: List<ComponentInfoInternal> = listOf(ComponentInfoInternal("a", "b", "c", "d", "e")),
resetLockoutRequiresHardwareAuthToken: Boolean = false
) = FingerprintSensorPropertiesInternal(
sensorId,
sensorStrength,
maxEnrollmentsPerUser,
info,
sensorType,
resetLockoutRequiresHardwareAuthToken,
listOf(this)
)

View File

@@ -165,7 +165,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private UdfpsKeyguardView mKeyguardView;
@Mock
private UdfpsKeyguardViewController mUdfpsKeyguardViewController;
private UdfpsBpView mBpView;
@Mock
private UdfpsFpmOtherView mFpmOtherView;
private UdfpsAnimationViewController mUdfpsKeyguardViewController =
mock(UdfpsKeyguardViewController.class);
@Mock
private TypedArray mBrightnessValues;
@Mock
@@ -192,6 +196,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<>();
@@ -340,7 +348,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,

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics
import android.graphics.PointF
import android.graphics.RectF
import android.hardware.biometrics.SensorLocationInternal
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.LayoutInflater
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.nullable
import org.mockito.Mockito.never
import org.mockito.Mockito.`when` as whenever
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val DISPLAY_ID = "" // default display id
private const val SENSOR_X = 50
private const val SENSOR_Y = 250
private const val SENSOR_RADIUS = 10
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class UdfpsViewTest : SysuiTestCase() {
@JvmField @Rule
var rule = MockitoJUnit.rule()
@Mock
lateinit var hbmProvider: UdfpsHbmProvider
@Mock
lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView>
private lateinit var view: UdfpsView
@Before
fun setup() {
context.setTheme(R.style.Theme_AppCompat)
context.orCreateTestableResources.addOverride(
com.android.internal.R.integer.config_udfps_illumination_transition_ms, 0)
view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView
view.animationViewController = animationViewController
view.sensorProperties =
SensorLocationInternal(DISPLAY_ID, SENSOR_X, SENSOR_Y, SENSOR_RADIUS)
.asFingerprintSensorProperties()
view.setHbmProvider(hbmProvider)
ViewUtils.attachView(view)
}
@After
fun cleanup() {
ViewUtils.detachView(view)
}
@Test
fun forwardsEvents() {
view.dozeTimeTick()
verify(animationViewController).dozeTimeTick()
view.onTouchOutsideView()
verify(animationViewController).onTouchOutsideView()
}
@Test
fun layoutSizeFitsSensor() {
val params = withArgCaptor<RectF> {
verify(animationViewController).onSensorRectUpdated(capture())
}
assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS)
assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS)
}
@Test
fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true)
@Test
fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false)
private fun isWithinSensorArea(paused: Boolean) {
whenever(animationViewController.shouldPauseAuth()).thenReturn(paused)
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
val end = (SENSOR_RADIUS * 2) - 1
for (x in 1 until end) {
for (y in 1 until end) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused)
}
}
}
@Test
fun isWithinSensorAreaWhenTranslated() {
val offset = PointF(100f, 200f)
whenever(animationViewController.touchTranslation).thenReturn(offset)
val end = (SENSOR_RADIUS * 2) - 1
for (x in 0 until offset.x.toInt() step 2) {
for (y in 0 until offset.y.toInt() step 2) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse()
}
}
for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) {
for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) {
assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue()
}
}
}
@Test
fun isNotWithinSensorArea() {
whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f))
assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())).isFalse()
assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse()
}
@Test
fun startAndStopIllumination() {
val onDone: Runnable = mock()
view.startIllumination(onDone)
val illuminator = withArgCaptor<Runnable> {
verify(hbmProvider).enableHbm(anyInt(), nullable(Surface::class.java), capture())
}
assertThat(view.isIlluminationRequested).isTrue()
verify(animationViewController).onIlluminationStarting()
verify(animationViewController, never()).onIlluminationStopped()
verify(onDone, never()).run()
// fake illumination event
illuminator.run()
waitForLooper()
verify(onDone).run()
verify(hbmProvider, never()).disableHbm(any())
view.stopIllumination()
assertThat(view.isIlluminationRequested).isFalse()
verify(animationViewController).onIlluminationStopped()
verify(hbmProvider).disableHbm(nullable(Runnable::class.java))
}
private fun waitForLooper() = TestableLooper.get(this).processAllMessages()
}