Merge changes from topic "sfps-updates" into sc-v2-dev

* changes:
  Update side fsp sensor position via overlay.
  Update side fingerprint sensor overlay assets.
This commit is contained in:
Joe Bolinger
2021-09-16 01:28:17 +00:00
committed by Android (Google) Code Review
31 changed files with 677 additions and 508 deletions

View File

@@ -0,0 +1,19 @@
/*
* 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 android.hardware.biometrics;
// @hide
parcelable SensorLocationInternal;

View File

@@ -0,0 +1,112 @@
/*
* 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 android.hardware.biometrics;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
/**
* The location of a sensor relative to a physical display.
*
* Note that the location may change depending on other attributes of the device, such as
* fold status, which are not yet included in this class.
* @hide
*/
public class SensorLocationInternal implements Parcelable {
/** Default value to use when the sensor's location is unknown or undefined. */
public static final SensorLocationInternal DEFAULT = new SensorLocationInternal("", 0, 0, 0);
/**
* The stable display id.
*/
@NonNull
public final String displayId;
/**
* The location of the center of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
* distance in pixels, measured from the left edge of the screen.
*/
public final int sensorLocationX;
/**
* The location of the center of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
* distance in pixels, measured from the top edge of the screen.
*
*/
public final int sensorLocationY;
/**
* The radius of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
* of the sensor, in pixels.
*/
public final int sensorRadius;
public SensorLocationInternal(@Nullable String displayId,
int sensorLocationX, int sensorLocationY, int sensorRadius) {
this.displayId = displayId != null ? displayId : "";
this.sensorLocationX = sensorLocationX;
this.sensorLocationY = sensorLocationY;
this.sensorRadius = sensorRadius;
}
protected SensorLocationInternal(Parcel in) {
displayId = in.readString16NoHelper();
sensorLocationX = in.readInt();
sensorLocationY = in.readInt();
sensorRadius = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(displayId);
dest.writeInt(sensorLocationX);
dest.writeInt(sensorLocationY);
dest.writeInt(sensorRadius);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<SensorLocationInternal> CREATOR =
new Creator<SensorLocationInternal>() {
@Override
public SensorLocationInternal createFromParcel(Parcel in) {
return new SensorLocationInternal(in);
}
@Override
public SensorLocationInternal[] newArray(int size) {
return new SensorLocationInternal[size];
}
};
@Override
public String toString() {
return "[id: " + displayId
+ ", x: " + sensorLocationX
+ ", y: " + sensorLocationY
+ ", r: " + sensorRadius + "]";
}
}

View File

@@ -21,7 +21,9 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFP
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.Parcel;
@@ -38,34 +40,14 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
*/
public final @FingerprintSensorProperties.SensorType int sensorType;
/**
* The location of the center of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
* distance in pixels, measured from the left edge of the screen.
*/
public final int sensorLocationX;
/**
* The location of the center of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the
* distance in pixels, measured from the top edge of the screen.
*
*/
public final int sensorLocationY;
/**
* The radius of the sensor if applicable. For example, sensors of type
* {@link FingerprintSensorProperties#TYPE_UDFPS_OPTICAL} would report this value as the radius
* of the sensor, in pixels.
*/
public final int sensorRadius;
private final List<SensorLocationInternal> mSensorLocations;
public FingerprintSensorPropertiesInternal(int sensorId,
@SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
@NonNull List<ComponentInfoInternal> componentInfo,
@FingerprintSensorProperties.SensorType int sensorType,
boolean resetLockoutRequiresHardwareAuthToken, int sensorLocationX, int sensorLocationY,
int sensorRadius) {
boolean resetLockoutRequiresHardwareAuthToken,
@NonNull List<SensorLocationInternal> sensorLocations) {
// IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not
// required as it can only be generated/attested/verified by TEE components.
// IFingerprint@1.0 handles lockout below the HAL, but does not require a challenge. See
@@ -73,9 +55,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
super(sensorId, strength, maxEnrollmentsPerUser, componentInfo,
resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */);
this.sensorType = sensorType;
this.sensorLocationX = sensorLocationX;
this.sensorLocationY = sensorLocationY;
this.sensorRadius = sensorRadius;
this.mSensorLocations = List.copyOf(sensorLocations);
}
/**
@@ -88,16 +68,15 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
boolean resetLockoutRequiresHardwareAuthToken) {
// TODO(b/179175438): Value should be provided from the HAL
this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType,
resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
1636 /* sensorLocationY */, 130 /* sensorRadius */);
resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal(
"" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */,
130 /* sensorRadius */)));
}
protected FingerprintSensorPropertiesInternal(Parcel in) {
super(in);
sensorType = in.readInt();
sensorLocationX = in.readInt();
sensorLocationY = in.readInt();
sensorRadius = in.readInt();
mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR);
}
public static final Creator<FingerprintSensorPropertiesInternal> CREATOR =
@@ -122,9 +101,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(sensorType);
dest.writeInt(sensorLocationX);
dest.writeInt(sensorLocationY);
dest.writeInt(sensorRadius);
dest.writeTypedList(mSensorLocations);
}
public boolean isAnyUdfpsType() {
@@ -150,6 +127,44 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna
}
}
/**
* Get the default location.
*
* Use this method when the sensor's relationship to the displays on the device do not
* matter.
* @return
*/
@NonNull
public SensorLocationInternal getLocation() {
final SensorLocationInternal location = getLocation("" /* displayId */);
return location != null ? location : SensorLocationInternal.DEFAULT;
}
/**
* Get the location of a sensor relative to a physical display layout.
*
* @param displayId stable display id
* @return location or null if none is specified
*/
@Nullable
public SensorLocationInternal getLocation(String displayId) {
for (SensorLocationInternal location : mSensorLocations) {
if (location.displayId.equals(displayId)) {
return location;
}
}
return null;
}
/**
* Gets all locations relative to all supported display layouts.
* @return supported locations
*/
@NonNull
public List<SensorLocationInternal> getAllLocations() {
return mSensorLocations;
}
@Override
public String toString() {
return "ID: " + sensorId + ", Strength: " + sensorStrength + ", Type: " + sensorType;

View File

@@ -4576,6 +4576,20 @@
-->
</integer-array>
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
from the HAL in the future. -->
<array name="config_sfps_sensor_props" translatable="false">
<!--
<array>
<item>displayId</item>
<item>sensorLocationX</item>
<item>sensorLocationY</item>
<item>sensorRadius</item>
<array>
-->
</array>
<!-- How long it takes for the HW to start illuminating after the illumination is requested. -->
<integer name="config_udfps_illumination_transition_ms">50</integer>

View File

@@ -2609,6 +2609,7 @@
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />

View File

@@ -14,11 +14,13 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<com.android.systemui.biometrics.SidefpsView
<com.airbnb.lottie.LottieAnimationView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/sidefps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/accessibility_fingerprint_label">
</com.android.systemui.biometrics.SidefpsView>
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sidefps_animation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/sfps_pulse"
android:contentDescription="@string/accessibility_fingerprint_label"/>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.media.AudioAttributes;
import android.os.Process;
@@ -330,8 +331,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private void updateLockIconLocation() {
if (mUdfpsSupported) {
FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
mView.setCenterLocation(new PointF(props.sensorLocationX, props.sensorLocationY),
props.sensorRadius);
final SensorLocationInternal location = props.getLocation();
mView.setCenterLocation(new PointF(location.sensorLocationX, location.sensorLocationY),
location.sensorRadius);
} else {
mView.setCenterLocation(
new PointF(mWidthPixels / 2,

View File

@@ -114,7 +114,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@VisibleForTesting
IBiometricSysuiReceiver mReceiver;
@VisibleForTesting
@NonNull final BiometricOrientationEventListener mOrientationListener;
@NonNull final BiometricDisplayListener mOrientationListener;
@Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -459,13 +459,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
mUdfpsEnrolledForUser = new SparseBooleanArray();
mOrientationListener = new BiometricOrientationEventListener(context,
mOrientationListener = new BiometricDisplayListener(
context,
displayManager,
handler,
BiometricDisplayListener.SensorType.Generic.INSTANCE,
() -> {
onOrientationChanged();
return Unit.INSTANCE;
},
displayManager,
handler);
});
mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;

View File

@@ -223,7 +223,7 @@ class AuthRippleController @Inject constructor(
private fun updateUdfpsDependentParams() {
authController.udfpsProps?.let {
if (it.size > 0) {
udfpsRadius = it[0].sensorRadius.toFloat()
udfpsRadius = it[0].location.sensorRadius.toFloat()
udfpsController = udfpsControllerProvider.get()
if (mView.isAttachedToWindow) {

View File

@@ -0,0 +1,89 @@
/*
* 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.hardware.display.DisplayManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Handler
import android.view.Surface
import com.android.systemui.biometrics.BiometricDisplayListener.SensorType.Generic
/**
* A listener for keeping overlays for biometric sensors aligned with the physical device
* device's screen. The [onChanged] will be dispatched on the [handler]
* whenever a relevant change to the device's configuration (orientation, fold, display change,
* etc.) may require the UI to change for the given [sensorType].
*/
class BiometricDisplayListener(
private val context: Context,
private val displayManager: DisplayManager,
private val handler: Handler,
private val sensorType: SensorType = SensorType.Generic,
private val onChanged: () -> Unit
) : DisplayManager.DisplayListener {
private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayRemoved(displayId: Int) {}
override fun onDisplayChanged(displayId: Int) {
val rotationChanged = didRotationChange()
when (sensorType) {
is SensorType.SideFingerprint -> onChanged()
else -> {
if (rotationChanged) {
onChanged()
}
}
}
}
private fun didRotationChange(): Boolean {
val rotation = context.display?.rotation ?: return false
val last = lastRotation
lastRotation = rotation
return last != rotation
}
/** Listen for changes. */
fun enable() {
displayManager.registerDisplayListener(this, handler)
}
/** Stop listening for changes. */
fun disable() {
displayManager.unregisterDisplayListener(this)
}
/**
* Type of sensor to determine what kind of display changes require layouts.
*
* The [Generic] type should be used in cases where the modality can vary, such as
* biometric prompt (and this object will likely change as multi-mode auth is added).
*/
sealed class SensorType {
object Generic : SensorType()
data class UnderDisplayFingerprint(
val properties: FingerprintSensorPropertiesInternal
) : SensorType()
data class SideFingerprint(
val properties: FingerprintSensorPropertiesInternal
) : SensorType()
}
}

View File

@@ -1,57 +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.hardware.display.DisplayManager
import android.os.Handler
import android.view.OrientationEventListener
import android.view.Surface
/**
* An [OrientationEventListener] that invokes the [onOrientationChanged] callback whenever
* the orientation of the device has changed in order to keep overlays for biometric sensors
* aligned with the device's screen.
*/
class BiometricOrientationEventListener(
private val context: Context,
private val onOrientationChanged: () -> Unit,
private val displayManager: DisplayManager,
private val handler: Handler
) : DisplayManager.DisplayListener {
private var lastRotation = context.display?.rotation ?: Surface.ROTATION_0
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayRemoved(displayId: Int) {}
override fun onDisplayChanged(displayId: Int) {
val rotation = context.display?.rotation ?: return
if (lastRotation != rotation) {
lastRotation = rotation
onOrientationChanged()
}
}
fun enable() {
displayManager.registerDisplayListener(this, handler)
}
fun disable() {
displayManager.unregisterDisplayListener(this)
}
}

View File

@@ -1,239 +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 static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
import kotlin.Unit;
/**
* Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
*/
@SysUISingleton
public class SidefpsController {
private static final String TAG = "SidefpsController";
@NonNull private final Context mContext;
@NonNull private final LayoutInflater mInflater;
private final FingerprintManager mFingerprintManager;
private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// TODO: update mDisplayHeight and mDisplayWidth for multi-display devices
private final int mDisplayHeight;
private final int mDisplayWidth;
private boolean mIsVisible = false;
@Nullable private SidefpsView mView;
static final int SFPS_AFFORDANCE_WIDTH = 50; // in default portrait mode
@NonNull
private final ISidefpsController mSidefpsControllerImpl = new ISidefpsController.Stub() {
@Override
public void show() {
mFgExecutor.execute(() -> {
SidefpsController.this.show();
mIsVisible = true;
});
}
@Override
public void hide() {
mFgExecutor.execute(() -> {
SidefpsController.this.hide();
mIsVisible = false;
});
}
};
@VisibleForTesting
final FingerprintSensorPropertiesInternal mSensorProps;
private final WindowManager.LayoutParams mCoreLayoutParams;
@Inject
public SidefpsController(@NonNull Context context,
@NonNull LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
@NonNull WindowManager windowManager,
@Main DelayableExecutor fgExecutor,
@NonNull DisplayManager displayManager,
@Main Handler handler) {
mContext = context;
mInflater = inflater;
mFingerprintManager = checkNotNull(fingerprintManager);
mWindowManager = windowManager;
mFgExecutor = fgExecutor;
mOrientationListener = new BiometricOrientationEventListener(
context,
() -> {
onOrientationChanged();
return Unit.INSTANCE;
},
displayManager,
handler);
mSensorProps = findFirstSidefps();
checkArgument(mSensorProps != null);
mCoreLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
getCoreLayoutParamFlags(),
PixelFormat.TRANSLUCENT);
mCoreLayoutParams.setTitle(TAG);
// Overrides default, avoiding status bars during layout
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;
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
mDisplayHeight = displayMetrics.heightPixels;
mDisplayWidth = displayMetrics.widthPixels;
mFingerprintManager.setSidefpsController(mSidefpsControllerImpl);
}
private void show() {
mView = (SidefpsView) mInflater.inflate(R.layout.sidefps_view, null, false);
mView.setSensorProperties(mSensorProps);
mWindowManager.addView(mView, computeLayoutParams());
mOrientationListener.enable();
}
private void hide() {
if (mView != null) {
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView = null;
} else {
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
}
mOrientationListener.disable();
}
private void onOrientationChanged() {
// If mView is null or if view is hidden, then return.
if (mView == null || !mIsVisible) {
return;
}
// If the overlay needs to be displayed with a new configuration, destroy the current
// overlay, and re-create and show the overlay with the updated LayoutParams.
hide();
show();
}
@Nullable
private FingerprintSensorPropertiesInternal findFirstSidefps() {
for (FingerprintSensorPropertiesInternal props :
mFingerprintManager.getSensorPropertiesInternal()) {
if (props.isAnySidefpsType()) {
// TODO(b/188690214): L155-L173 can be removed once sensorLocationX,
// sensorLocationY, and sensorRadius are defined in sensorProps by the HAL
int sensorLocationX = 25;
int sensorLocationY = 610;
int sensorRadius = 112;
FingerprintSensorPropertiesInternal tempProps =
new FingerprintSensorPropertiesInternal(
props.sensorId,
props.sensorStrength,
props.maxEnrollmentsPerUser,
props.componentInfo,
props.sensorType,
props.resetLockoutRequiresHardwareAuthToken,
sensorLocationX,
sensorLocationY,
sensorRadius
);
props = tempProps;
return props;
}
}
return null;
}
private int getCoreLayoutParamFlags() {
return WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
/**
* Computes layout params depending on orientation & folding configuration of device
*/
private WindowManager.LayoutParams computeLayoutParams() {
mCoreLayoutParams.flags = getCoreLayoutParamFlags();
// Y value of top of affordance in portrait mode, X value of left of affordance in landscape
int sfpsLocationY = mSensorProps.sensorLocationY - mSensorProps.sensorRadius;
int sfpsAffordanceHeight = mSensorProps.sensorRadius * 2;
// Calculate coordinates of drawable area for the fps affordance, accounting for orientation
switch (mContext.getDisplay().getRotation()) {
case Surface.ROTATION_90:
mCoreLayoutParams.x = sfpsLocationY;
mCoreLayoutParams.y = 0;
mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
mCoreLayoutParams.width = sfpsAffordanceHeight;
break;
case Surface.ROTATION_270:
mCoreLayoutParams.x = mDisplayHeight - sfpsLocationY - sfpsAffordanceHeight;
mCoreLayoutParams.y = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
mCoreLayoutParams.height = SFPS_AFFORDANCE_WIDTH;
mCoreLayoutParams.width = sfpsAffordanceHeight;
break;
default: // Portrait
mCoreLayoutParams.x = mDisplayWidth - SFPS_AFFORDANCE_WIDTH;
mCoreLayoutParams.y = sfpsLocationY;
mCoreLayoutParams.height = sfpsAffordanceHeight;
mCoreLayoutParams.width = SFPS_AFFORDANCE_WIDTH;
}
return mCoreLayoutParams;
}
}

View File

@@ -0,0 +1,182 @@
/*
* 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.PixelFormat
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.util.Log
import android.view.Display
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.WindowManager
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
private const val TAG = "SidefpsController"
/**
* Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
*/
@SysUISingleton
class SidefpsController @Inject constructor(
private val context: Context,
private val layoutInflater: LayoutInflater,
fingerprintManager: FingerprintManager?,
private val windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
displayManager: DisplayManager,
@Main handler: Handler
) {
@VisibleForTesting
val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
?.sensorPropertiesInternal
?.firstOrNull { it.isAnySidefpsType }
?: throw IllegalStateException("no side fingerprint sensor")
@VisibleForTesting
val orientationListener = BiometricDisplayListener(
context,
displayManager,
handler,
BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
) { onOrientationChanged() }
private var overlayView: View? = null
set(value) {
field?.let { oldView ->
windowManager.removeView(oldView)
orientationListener.disable()
}
field = value
field?.let { newView ->
windowManager.addView(newView, overlayViewParams)
orientationListener.enable()
}
}
private val overlayViewParams = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT
).apply {
title = TAG
fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
gravity = Gravity.TOP or Gravity.LEFT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
}
init {
fingerprintManager?.setSidefpsController(object : ISidefpsController.Stub() {
override fun show() = mainExecutor.execute {
if (overlayView == null) {
overlayView = createOverlayForDisplay()
} else {
Log.v(TAG, "overlay already shown")
}
}
override fun hide() = mainExecutor.execute { overlayView = null }
})
}
private fun onOrientationChanged() {
if (overlayView != null) {
overlayView = createOverlayForDisplay()
}
}
private fun createOverlayForDisplay(): View {
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
val display = context.display!!
val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
lottie.setAnimation(display.asSideFpsAnimation())
view.rotation = display.asSideFpsAnimationRotation()
updateOverlayParams(display, lottie.composition?.bounds ?: Rect())
lottie.addLottieOnCompositionLoadedListener {
if (overlayView == view) {
updateOverlayParams(display, it.bounds)
windowManager.updateViewLayout(overlayView, overlayViewParams)
}
}
return view
}
private fun updateOverlayParams(display: Display, bounds: Rect) {
val isPortrait = display.isPortrait()
val size = windowManager.maximumWindowMetrics.bounds
val displayWidth = if (isPortrait) size.width() else size.height()
val displayHeight = if (isPortrait) size.height() else size.width()
val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
if (location == null) {
Log.w(TAG, "No location specified for display: ${display.uniqueId}")
}
location ?: sensorProps.location
}
// ignore sensorLocationX and sensorRadius since it's assumed to be on the side
// of the device and centered at sensorLocationY
val (x, y) = when (display.rotation) {
Surface.ROTATION_90 ->
Pair(offsets.sensorLocationY, 0)
Surface.ROTATION_270 ->
Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth)
Surface.ROTATION_180 ->
Pair(0, displayHeight - offsets.sensorLocationY - bounds.height())
else ->
Pair(displayWidth, offsets.sensorLocationY)
}
overlayViewParams.x = x
overlayViewParams.y = y
}
}
@RawRes
private fun Display.asSideFpsAnimation(): Int = when (rotation) {
Surface.ROTATION_0 -> R.raw.sfps_pulse
Surface.ROTATION_180 -> R.raw.sfps_pulse
else -> R.raw.sfps_pulse_landscape
}
private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
Surface.ROTATION_180 -> 180f
Surface.ROTATION_270 -> 180f
else -> 0f
}
private fun Display.isPortrait(): Boolean =
rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180

View File

@@ -1,107 +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 static com.android.systemui.biometrics.SidefpsController.SFPS_AFFORDANCE_WIDTH;
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.AttributeSet;
import android.view.Surface;
import android.widget.FrameLayout;
/**
* A view containing a normal drawable view for sidefps events.
*/
public class SidefpsView extends FrameLayout {
private static final String TAG = "SidefpsView";
private static final int POINTER_SIZE_PX = 50;
private static final int ROUND_RADIUS = 15;
@NonNull private final RectF mSensorRect;
@NonNull private final Paint mSensorRectPaint;
@NonNull private final Paint mPointerText;
@NonNull private final Context mContext;
// Used to obtain the sensor location.
@NonNull private FingerprintSensorPropertiesInternal mSensorProps;
@Surface.Rotation private int mOrientation;
public SidefpsView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setWillNotDraw(false);
mContext = context;
mPointerText = new Paint(0 /* flags */);
mPointerText.setAntiAlias(true);
mPointerText.setColor(Color.WHITE);
mPointerText.setTextSize(POINTER_SIZE_PX);
mSensorRect = new RectF();
mSensorRectPaint = new Paint(0 /* flags */);
mSensorRectPaint.setAntiAlias(true);
mSensorRectPaint.setColor(Color.BLUE); // TODO: Fix Color
mSensorRectPaint.setStyle(Paint.Style.FILL);
}
void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
mSensorProps = properties;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRoundRect(mSensorRect, ROUND_RADIUS, ROUND_RADIUS, mSensorRectPaint);
int x, y;
if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
x = mSensorProps.sensorRadius + 10;
y = SFPS_AFFORDANCE_WIDTH / 2 + 15;
} else {
x = SFPS_AFFORDANCE_WIDTH / 2 - 10;
y = mSensorProps.sensorRadius + 30;
}
canvas.drawText(
">",
x,
y,
mPointerText
);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mOrientation = mContext.getDisplay().getRotation();
if (mOrientation == Surface.ROTATION_90 || mOrientation == Surface.ROTATION_270) {
right = mSensorProps.sensorRadius * 2;
bottom = SFPS_AFFORDANCE_WIDTH;
} else {
right = SFPS_AFFORDANCE_WIDTH;
bottom = mSensorProps.sensorRadius * 2;
}
mSensorRect.set(
0,
0,
right,
bottom);
}
}

View File

@@ -33,6 +33,7 @@ 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;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -129,7 +130,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final KeyguardBypassController mKeyguardBypassController;
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final SystemClock mSystemClock;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
@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;
@@ -558,14 +559,6 @@ public class UdfpsController implements DozeReceiver {
mHbmProvider = hbmProvider.orElse(null);
screenLifecycle.addObserver(mScreenObserver);
mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
mOrientationListener = new BiometricOrientationEventListener(
context,
() -> {
onOrientationChanged();
return Unit.INSTANCE;
},
displayManager,
mainHandler);
mKeyguardBypassController = keyguardBypassController;
mConfigurationController = configurationController;
mSystemClock = systemClock;
@@ -573,6 +566,15 @@ public class UdfpsController implements DozeReceiver {
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
checkArgument(mSensorProps != null);
mOrientationListener = new BiometricDisplayListener(
context,
displayManager,
mainHandler,
new BiometricDisplayListener.SensorType.UnderDisplayFingerprint(mSensorProps),
() -> {
onOrientationChanged();
return Unit.INSTANCE;
});
mCoreLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
@@ -642,10 +644,11 @@ public class UdfpsController implements DozeReceiver {
// on lockscreen and for the udfps light reveal animation on keyguard.
// Keyguard is only shown in portrait mode for now, so this will need to
// be updated if that ever changes.
return new RectF(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
final SensorLocationInternal location = mSensorProps.getLocation();
return new RectF(location.sensorLocationX - location.sensorRadius,
location.sensorLocationY - location.sensorRadius,
location.sensorLocationX + location.sensorRadius,
location.sensorLocationY + location.sensorRadius);
}
private void updateOverlay() {
@@ -669,10 +672,11 @@ public class UdfpsController implements DozeReceiver {
}
// Default dimensions assume portrait mode.
mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX;
mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY;
mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX;
mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY;
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.
@@ -685,9 +689,9 @@ public class UdfpsController implements DozeReceiver {
&& mKeyguardUpdateMonitor.isGoingToSleep()) {
break;
}
mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius
- paddingX;
mCoreLayoutParams.y = p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius
mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius
- paddingY;
break;
@@ -696,9 +700,9 @@ public class UdfpsController implements DozeReceiver {
&& mKeyguardUpdateMonitor.isGoingToSleep()) {
break;
}
mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius
- paddingX;
mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius
mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius
- paddingY;
break;

View File

@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.util.Log;
import android.view.Surface;
@@ -93,7 +94,7 @@ public class UdfpsDialogMeasureAdapter {
// Go through each of the children and do the custom measurement.
int totalHeight = 0;
final int numChildren = mView.getChildCount();
final int sensorDiameter = mSensorProps.sensorRadius * 2;
final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2;
for (int i = 0; i < numChildren; i++) {
final View child = mView.getChildAt(i);
if (child.getId() == R.id.biometric_icon_frame) {
@@ -160,7 +161,7 @@ public class UdfpsDialogMeasureAdapter {
final int horizontalSpacerWidth = calculateHorizontalSpacerWidthForLandscape(
mSensorProps, displayWidth, dialogMargin, horizontalInset);
final int sensorDiameter = mSensorProps.sensorRadius * 2;
final int sensorDiameter = mSensorProps.getLocation().sensorRadius * 2;
final int remeasuredWidth = sensorDiameter + 2 * horizontalSpacerWidth;
int remeasuredHeight = 0;
@@ -257,10 +258,10 @@ public class UdfpsDialogMeasureAdapter {
@NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayHeightPx,
int textIndicatorHeightPx, int buttonBarHeightPx, int dialogMarginPx,
int navbarBottomInsetPx) {
final SensorLocationInternal location = sensorProperties.getLocation();
final int sensorDistanceFromBottom = displayHeightPx
- sensorProperties.sensorLocationY
- sensorProperties.sensorRadius;
- location.sensorLocationY
- location.sensorRadius;
final int spacerHeight = sensorDistanceFromBottom
- textIndicatorHeightPx
@@ -318,10 +319,10 @@ public class UdfpsDialogMeasureAdapter {
static int calculateHorizontalSpacerWidthForLandscape(
@NonNull FingerprintSensorPropertiesInternal sensorProperties, int displayWidthPx,
int dialogMarginPx, int navbarHorizontalInsetPx) {
final SensorLocationInternal location = sensorProperties.getLocation();
final int sensorDistanceFromEdge = displayWidthPx
- sensorProperties.sensorLocationY
- sensorProperties.sensorRadius;
- location.sensorLocationY
- location.sensorRadius;
final int horizontalPadding = sensorDistanceFromEdge
- dialogMarginPx

View File

@@ -63,7 +63,7 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
final int sensorHeight = sensorProps.sensorRadius * 2;
final int sensorHeight = sensorProps.getLocation().sensorRadius * 2;
final int sensorWidth = sensorHeight;
ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
params.width = sensorWidth;

View File

@@ -25,6 +25,7 @@ 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;
@@ -141,11 +142,12 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
: mAnimationViewController.getPaddingX();
int paddingY = mAnimationViewController == null ? 0
: mAnimationViewController.getPaddingY();
final SensorLocationInternal location = mSensorProps.getLocation();
mSensorRect.set(
paddingX,
paddingY,
2 * mSensorProps.sensorRadius + paddingX,
2 * mSensorProps.sensorRadius + paddingY);
2 * location.sensorRadius + paddingX,
2 * location.sensorRadius + paddingY);
if (mAnimationViewController != null) {
mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect));

View File

@@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
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;
@@ -257,9 +258,10 @@ public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase {
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */,
540 /* sensorLocationX */,
1600 /* sensorLocationY */,
100 /* sensorRadius */);
List.of(new SensorLocationInternal("" /* displayId */,
540 /* sensorLocationX */,
1600 /* sensorLocationY */,
100 /* sensorRadius */)));
}
public class TestableView extends AuthBiometricFaceToFingerprintView {

View File

@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
import android.graphics.Rect
import android.hardware.biometrics.SensorProperties
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManagerGlobal
@@ -30,8 +31,12 @@ import android.view.Display
import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
import android.view.DisplayInfo
import android.view.LayoutInflater
import android.view.View
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
@@ -42,9 +47,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -66,11 +75,13 @@ class SidefpsControllerTest : SysuiTestCase() {
@Mock
lateinit var windowManager: WindowManager
@Mock
lateinit var sidefpsView: SidefpsView
lateinit var sidefpsView: View
@Mock
lateinit var displayManager: DisplayManager
@Mock
lateinit var handler: Handler
@Captor
lateinit var overlayCaptor: ArgumentCaptor<View>
private val executor = FakeExecutor(FakeSystemClock())
private lateinit var overlayController: ISidefpsController
@@ -79,6 +90,8 @@ class SidefpsControllerTest : SysuiTestCase() {
@Before
fun setup() {
`when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
`when`(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
.thenReturn(mock(LottieAnimationView::class.java))
`when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
listOf(
FingerprintSensorPropertiesInternal(
@@ -99,6 +112,9 @@ class SidefpsControllerTest : SysuiTestCase() {
DEFAULT_DISPLAY_ADJUSTMENTS
)
)
`when`(windowManager.maximumWindowMetrics).thenReturn(
WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED)
)
sideFpsController = SidefpsController(
mContext, layoutInflater, fingerprintManager, windowManager, executor,
@@ -121,4 +137,44 @@ class SidefpsControllerTest : SysuiTestCase() {
executor.runAllReady()
verify(displayManager).unregisterDisplayListener(any())
}
@Test
fun testShowsAndHides() {
overlayController.show()
executor.runAllReady()
verify(windowManager).addView(overlayCaptor.capture(), any())
reset(windowManager)
overlayController.hide()
executor.runAllReady()
verify(windowManager, never()).addView(any(), any())
verify(windowManager).removeView(eq(overlayCaptor.value))
}
@Test
fun testShowsOnce() {
repeat(5) {
overlayController.show()
executor.runAllReady()
}
verify(windowManager).addView(any(), any())
verify(windowManager, never()).removeView(any())
}
@Test
fun testHidesOnce() {
overlayController.show()
executor.runAllReady()
repeat(5) {
overlayController.hide()
executor.runAllReady()
}
verify(windowManager).addView(any(), any())
verify(windowManager).removeView(any())
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.biometrics;
import static org.junit.Assert.assertEquals;
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;
@@ -61,8 +62,9 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
sensorRadius);
true /* resetLockoutRequiresHardwareAuthToken */,
List.of(new SensorLocationInternal("" /* displayId */,
sensorLocationX, sensorLocationY, sensorRadius)));
assertEquals(970,
UdfpsDialogMeasureAdapter.calculateBottomSpacerHeightForPortrait(
@@ -125,8 +127,9 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase {
0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
true /* resetLockoutRequiresHardwareAuthToken */, sensorLocationX, sensorLocationY,
sensorRadius);
true /* resetLockoutRequiresHardwareAuthToken */,
List.of(new SensorLocationInternal("" /* displayId */,
sensorLocationX, sensorLocationY, sensorRadius)));
assertEquals(1205,
UdfpsDialogMeasureAdapter.calculateHorizontalSpacerWidthForLandscape(

View File

@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
@@ -132,7 +133,8 @@ public class LockIconViewControllerTest extends SysuiTestCase {
/* component info */ new ArrayList<>(),
/* sensorType */ 3,
/* resetLockoutRequiresHwToken */ false,
(int) udfpsLocation.x, (int) udfpsLocation.y, radius);
List.of(new SensorLocationInternal("" /* displayId */,
(int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
@@ -165,7 +167,8 @@ public class LockIconViewControllerTest extends SysuiTestCase {
/* component info */ new ArrayList<>(),
/* sensorType */ 3,
/* resetLockoutRequiresHwToken */ false,
(int) udfpsLocation.x, (int) udfpsLocation.y, radius);
List.of(new SensorLocationInternal("" /* displayId */,
(int) udfpsLocation.x, (int) udfpsLocation.y, radius)));
when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));

View File

@@ -47,6 +47,7 @@ import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -766,8 +767,9 @@ public class AuthService extends SystemService {
if (isUdfps && udfpsProps.length == 3) {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, udfpsProps[0],
udfpsProps[1], udfpsProps[2]);
componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken,
List.of(new SensorLocationInternal("" /* display */,
udfpsProps[0], udfpsProps[1], udfpsProps[2])));
} else {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,

View File

@@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -67,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
int sensorId, boolean isStrongBiometric, int statsClient,
@Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
@@ -76,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = createALSCallback(false /* startWithClient */);
}

View File

@@ -25,10 +25,12 @@ import android.app.ActivityTaskManager;
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.TypedArray;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
@@ -145,6 +147,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -164,9 +168,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
componentInfo,
prop.sensorType,
true /* resetLockoutRequiresHardwareAuthToken */,
prop.sensorLocations[0].sensorLocationX,
prop.sensorLocations[0].sensorLocationY,
prop.sensorLocations[0].sensorRadius);
!workaroundLocations.isEmpty() ? workaroundLocations :
List.of(new SensorLocationInternal(
"" /* displayId */,
prop.sensorLocations[0].sensorLocationX,
prop.sensorLocations[0].sensorLocationY,
prop.sensorLocations[0].sensorRadius)));
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
@@ -403,7 +410,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication,
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
@@ -647,4 +654,45 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
void setTestHalEnabled(boolean enabled) {
mTestHalEnabled = enabled;
}
// TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
// reads values via an overlay instead of querying the HAL
@NonNull
private List<SensorLocationInternal> getWorkaroundSensorProps(@NonNull Context context) {
final List<SensorLocationInternal> sensorLocations = new ArrayList<>();
final TypedArray sfpsProps = context.getResources().obtainTypedArray(
com.android.internal.R.array.config_sfps_sensor_props);
for (int i = 0; i < sfpsProps.length(); i++) {
final int id = sfpsProps.getResourceId(i, -1);
if (id > 0) {
final SensorLocationInternal location = parseSensorLocation(
context.getResources().obtainTypedArray(id));
if (location != null) {
sensorLocations.add(location);
}
}
}
sfpsProps.recycle();
return sensorLocations;
}
@Nullable
private SensorLocationInternal parseSensorLocation(@Nullable TypedArray array) {
if (array == null) {
return null;
}
try {
return new SensorLocationInternal(
array.getString(0),
array.getInt(1, 0),
array.getInt(2, 0),
array.getInt(3, 0));
} catch (Exception e) {
Slog.w(getTag(), "malformed sensor location", e);
}
return null;
}
}

View File

@@ -629,7 +629,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
restricted, opPackageName, cookie, false /* requireConfirmation */,
mSensorProperties.sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
mTaskStackListener, mLockoutTracker,
mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication, mSensorProperties);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});

View File

@@ -426,8 +426,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
resetLockoutRequiresHardwareAuthToken, sensorProps.sensorLocationX,
sensorProps.sensorLocationY, sensorProps.sensorRadius);
resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
mMockHalResultController = controller;
mUserHasTrust = new SparseBooleanArray();
mTrustManager = context.getSystemService(TrustManager.class);

View File

@@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -67,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@NonNull TaskStackListener taskStackListener,
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
@@ -76,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = createALSCallback(false /* startWithClient */);
}

View File

@@ -19,10 +19,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorLocation;
@@ -56,6 +59,8 @@ public class FingerprintProviderTest {
@Mock
private Context mContext;
@Mock
private Resources mResources;
@Mock
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@@ -74,19 +79,21 @@ public class FingerprintProviderTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.obtainTypedArray(anyInt())).thenReturn(mock(TypedArray.class));
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
sensor1.sensorLocations = new SensorLocation[] {new SensorLocation()};
sensor1.sensorLocations = new SensorLocation[]{new SensorLocation()};
final SensorProps sensor2 = new SensorProps();
sensor2.commonProps = new CommonProps();
sensor2.commonProps.sensorId = 1;
sensor2.sensorLocations = new SensorLocation[] {new SensorLocation()};
sensor2.sensorLocations = new SensorLocation[]{new SensorLocation()};
mSensorProps = new SensorProps[] {sensor1, sensor2};
mSensorProps = new SensorProps[]{sensor1, sensor2};
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);