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:
@@ -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"/>
|
||||
|
||||
1
packages/SystemUI/res/raw/sfps_pulse.json
Normal file
1
packages/SystemUI/res/raw/sfps_pulse.json
Normal file
File diff suppressed because one or more lines are too long
1
packages/SystemUI/res/raw/sfps_pulse_landscape.json
Normal file
1
packages/SystemUI/res/raw/sfps_pulse_landscape.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user