Update side fingerprint sensor overlay assets.

The UI offsets are still missing so the alignment is not complete and some other parts of systemui can overlap in certain configurations.

Bug: 197265282
Test: atest SidefpsControllerTest
Test: manual (enroll & rotate device)
Change-Id: Iaf7d8d2fd6e4204541857fabdd7c76999645c541
This commit is contained in:
Joe Bolinger
2021-08-27 15:40:07 -07:00
parent dce40910b1
commit 33c8a1a769
15 changed files with 357 additions and 429 deletions

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

@@ -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

@@ -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,174 @@
/*
* 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.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
?.filter { it.isAnySidefpsType }
// TODO(b/188690214): remove - should directly come from HAL
?.map { props ->
FingerprintSensorPropertiesInternal(
props.sensorId,
props.sensorStrength,
props.maxEnrollmentsPerUser,
props.componentInfo,
props.sensorType,
props.resetLockoutRequiresHardwareAuthToken,
25 /* sensorLocationX */,
610 /* sensorLocationY */,
112 /* sensorRadius */
)
}?.firstOrNull() ?: 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 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 lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
lottie.setAnimation(display.asSideFpsAnimation())
view.rotation = display.asSideFpsAnimationRotation()
// 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(sensorProps.sensorLocationY, 0)
Surface.ROTATION_270 -> Pair(displayHeight - sensorProps.sensorLocationY, displayWidth)
Surface.ROTATION_180 -> Pair(0, displayHeight - sensorProps.sensorLocationY)
else -> Pair(displayWidth, sensorProps.sensorLocationY)
}
overlayViewParams.x = x
overlayViewParams.y = y
return view
}
}
@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

@@ -129,7 +129,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 +558,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 +565,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,

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

@@ -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

@@ -403,7 +403,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);
});

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

@@ -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 */);
}