diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index cad5aeb8b3808..34513b6284438 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1369,7 +1369,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.endSection(); // on auth success, we sometimes never received an acquired haptic - if (!mPlayedAcquiredHaptic) { + if (!mPlayedAcquiredHaptic && isUdfpsEnrolled()) { playAcquiredHaptic(); } } @@ -1387,7 +1387,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationAcquired(int acquireInfo) { handleFingerprintAcquired(acquireInfo); - if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) { + if (acquireInfo == FingerprintManager.FINGERPRINT_ACQUIRED_GOOD + && isUdfpsEnrolled()) { + mPlayedAcquiredHaptic = true; playAcquiredHaptic(); } } @@ -1402,20 +1404,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab public void onUdfpsPointerUp(int sensorId) { Log.d(TAG, "onUdfpsPointerUp, sensorId: " + sensorId); } - - private void playAcquiredHaptic() { - if (mAcquiredHapticEnabled && mVibrator != null && isUdfpsEnrolled()) { - mPlayedAcquiredHaptic = true; - String effect = Settings.Global.getString( - mContext.getContentResolver(), - "udfps_acquired_type"); - mVibrator.vibrate(UdfpsController.getVibration(effect, - UdfpsController.EFFECT_TICK), - UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES); - } - } }; + /** + * Play haptic to signal udfps fingeprrint acquired. + */ + @VisibleForTesting + public void playAcquiredHaptic() { + if (mAcquiredHapticEnabled && mVibrator != null) { + String effect = Settings.Global.getString( + mContext.getContentResolver(), + "udfps_acquired_type"); + mVibrator.vibrate(UdfpsController.getVibration(effect, + UdfpsController.EFFECT_TICK), + UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES); + } + } + private final FaceManager.FaceDetectionCallback mFaceDetectionCallback = (sensorId, userId, isStrongBiometric) -> { // Trigger the face success path so the bouncer can be shown diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 3d2c4e1dafe86..2d04d8da0cdcc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -430,21 +430,7 @@ public class UdfpsController implements DozeReceiver { mTouchLogTime = SystemClock.elapsedRealtime(); mPowerManager.userActivity(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); - - // TODO: this should eventually be removed after ux testing - if (mVibrator != null) { - final ContentResolver contentResolver = - mContext.getContentResolver(); - int startEnabled = Settings.Global.getInt(contentResolver, - "udfps_start", 1); - if (startEnabled > 0) { - String startEffectSetting = Settings.Global.getString( - contentResolver, "udfps_start_type"); - mVibrator.vibrate(getVibration(startEffectSetting, - EFFECT_CLICK), VIBRATION_SONIFICATION_ATTRIBUTES); - } - } - + playStartHaptic(); handled = true; } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { Log.v(TAG, "onTouch | finger move: " + touchInfo); @@ -498,6 +484,7 @@ public class UdfpsController implements DozeReceiver { @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController, @NonNull ScreenLifecycle screenLifecycle, @Nullable Vibrator vibrator, + @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @NonNull Optional hbmProvider) { mContext = context; mExecution = execution; @@ -544,6 +531,29 @@ public class UdfpsController implements DozeReceiver { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + + udfpsHapticsSimulator.setUdfpsController(this); + } + + /** + * Play haptic to signal udfps scanning started. + */ + @VisibleForTesting + public void playStartHaptic() { + if (mVibrator != null) { + final ContentResolver contentResolver = + mContext.getContentResolver(); + // TODO: these settings checks should eventually be removed after ux testing + // (b/185124905) + int startEnabled = Settings.Global.getInt(contentResolver, + "udfps_start", 1); + if (startEnabled > 0) { + String startEffectSetting = Settings.Global.getString( + contentResolver, "udfps_start_type"); + mVibrator.vibrate(getVibration(startEffectSetting, + EFFECT_CLICK), VIBRATION_SONIFICATION_ATTRIBUTES); + } + } } private int getCoreLayoutParamFlags() { @@ -836,7 +846,6 @@ public class UdfpsController implements DozeReceiver { } } - /** * get vibration to play given string * used for testing purposes (b/185124905) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt new file mode 100644 index 0000000000000..ea2bbfad1b74b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt @@ -0,0 +1,94 @@ +/* + * 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.media.AudioAttributes +import android.os.VibrationEffect +import android.os.Vibrator + +import com.android.keyguard.KeyguardUpdateMonitor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry + +import java.io.PrintWriter + +import javax.inject.Inject + +/** + * Used to simulate haptics that may be used for udfps authentication. + */ +@SysUISingleton +class UdfpsHapticsSimulator @Inject constructor( + commandRegistry: CommandRegistry, + val vibrator: Vibrator?, + val keyguardUpdateMonitor: KeyguardUpdateMonitor +) : Command { + val sonificationEffects = + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .build() + var udfpsController: UdfpsController? = null + + init { + commandRegistry.registerCommand("udfps-haptic") { this } + } + + override fun execute(pw: PrintWriter, args: List) { + if (args.isEmpty()) { + invalidCommand(pw) + } else { + when (args[0]) { + "start" -> { + udfpsController?.playStartHaptic() + } + "acquired" -> { + keyguardUpdateMonitor.playAcquiredHaptic() + } + "success" -> { + // needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT + vibrator?.vibrate( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK), + sonificationEffects) + } + "error" -> { + // needs to be kept up to date with AcquisitionClient#ERROR_VIBRATION_EFFECT + vibrator?.vibrate( + VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), + sonificationEffects) + } + else -> invalidCommand(pw) + } + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: adb shell cmd statusbar udfps-haptic ") + pw.println("Available commands:") + pw.println(" start") + pw.println(" acquired") + pw.println(" success, always plays CLICK haptic") + pw.println(" error, always plays DOUBLE_CLICK haptic") + } + + fun invalidCommand(pw: PrintWriter) { + pw.println("invalid command") + help(pw) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 8ab32bb401058..25722e1c956b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -126,6 +126,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private ScreenLifecycle mScreenLifecycle; @Mock private Vibrator mVibrator; + @Mock + private UdfpsHapticsSimulator mUdfpsHapticsSimulator; private FakeExecutor mFgExecutor; @@ -188,6 +190,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mLockscreenShadeTransitionController, mScreenLifecycle, mVibrator, + mUdfpsHapticsSimulator, Optional.of(mHbmProvider)); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 28e23e32c8db2..c8fb80ac39288 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -17,7 +17,6 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.media.AudioAttributes; @@ -27,7 +26,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.VibrationEffect; import android.os.Vibrator; -import android.text.TextUtils; import android.util.Slog; /** @@ -39,24 +37,18 @@ public abstract class AcquisitionClient extends HalClientMonitor implement private static final String TAG = "Biometrics/AcquisitionClient"; - private static final AudioAttributes VIBRATION_SONFICATION_ATTRIBUTES = + private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build(); - private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK); - private final VibrationEffect mEffectTextureTick = - VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK); - private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - private final VibrationEffect mEffectHeavy = - VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK); - private final VibrationEffect mDoubleClick = + private static final VibrationEffect SUCCESS_VIBRATION_EFFECT = + VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + private static final VibrationEffect ERROR_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); private final PowerManager mPowerManager; - private final VibrationEffect mSuccessVibrationEffect; - private final VibrationEffect mErrorVibrationEffect; private boolean mShouldSendErrorToClient = true; private boolean mAlreadyCancelled; @@ -72,8 +64,6 @@ public abstract class AcquisitionClient extends HalClientMonitor implement super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality, statsAction, statsClient); mPowerManager = context.getSystemService(PowerManager.class); - mSuccessVibrationEffect = mEffectClick; - mErrorVibrationEffect = mDoubleClick; } @Override @@ -192,49 +182,31 @@ public abstract class AcquisitionClient extends HalClientMonitor implement mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); } - protected @Nullable VibrationEffect getSuccessVibrationEffect() { - return mSuccessVibrationEffect; + protected boolean successHapticsEnabled() { + return true; } - protected @Nullable VibrationEffect getErrorVibrationEffect() { - return mErrorVibrationEffect; + protected boolean errorHapticsEnabled() { + return true; } protected final void vibrateSuccess() { + if (!successHapticsEnabled()) { + return; + } Vibrator vibrator = getContext().getSystemService(Vibrator.class); - VibrationEffect effect = getSuccessVibrationEffect(); - if (vibrator != null && effect != null) { - vibrator.vibrate(effect, VIBRATION_SONFICATION_ATTRIBUTES); + if (vibrator != null) { + vibrator.vibrate(SUCCESS_VIBRATION_EFFECT, VIBRATION_SONIFICATION_ATTRIBUTES); } } protected final void vibrateError() { + if (!errorHapticsEnabled()) { + return; + } Vibrator vibrator = getContext().getSystemService(Vibrator.class); - VibrationEffect effect = getErrorVibrationEffect(); - if (vibrator != null && effect != null) { - vibrator.vibrate(effect, VIBRATION_SONFICATION_ATTRIBUTES); - } - } - - protected final @NonNull VibrationEffect getVibration(@Nullable String effect, - @NonNull VibrationEffect defaultEffect) { - if (TextUtils.isEmpty(effect)) { - return defaultEffect; - } - - switch (effect.toLowerCase()) { - case "click": - return mEffectClick; - case "heavy": - return mEffectHeavy; - case "texture_tick": - return mEffectTextureTick; - case "tick": - return mEffectTick; - case "double_click": - return mDoubleClick; - default: - return defaultEffect; + if (vibrator != null) { + vibrator.vibrate(ERROR_VIBRATION_EFFECT, VIBRATION_SONIFICATION_ATTRIBUTES); } } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 6b9ff6f35128a..cf545f37bd431 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -22,7 +22,6 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.hardware.biometrics.BiometricAuthenticator; @@ -31,8 +30,6 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; -import android.os.VibrationEffect; -import android.provider.Settings; import android.security.KeyStore; import android.util.EventLog; import android.util.Slog; @@ -59,14 +56,12 @@ public abstract class AuthenticationClient extends AcquisitionClient private final LockoutTracker mLockoutTracker; private final boolean mIsRestricted; private final boolean mAllowBackgroundAuthentication; - @NonNull private final ContentResolver mContentResolver; protected final long mOperationId; private long mStartTimeMs; protected boolean mAuthAttempted; - private final boolean mCustomHaptics; public AuthenticationClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, @@ -85,10 +80,6 @@ public abstract class AuthenticationClient extends AcquisitionClient mLockoutTracker = lockoutTracker; mIsRestricted = restricted; mAllowBackgroundAuthentication = allowBackgroundAuthentication; - - mContentResolver = context.getContentResolver(); - mCustomHaptics = Settings.Global.getInt(mContentResolver, - "fp_custom_success_error", 0) == 1; } public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) { @@ -342,33 +333,4 @@ public abstract class AuthenticationClient extends AcquisitionClient public boolean interruptsPrecedingClients() { return true; } - - @Override - protected @Nullable VibrationEffect getSuccessVibrationEffect() { - if (!mCustomHaptics) { - return super.getSuccessVibrationEffect(); - } - - if (Settings.Global.getInt(mContentResolver, "fp_success_enabled", 1) == 0) { - return null; - } - - return getVibration(Settings.Global.getString(mContentResolver, - "fp_success_type"), super.getSuccessVibrationEffect()); - } - - @Override - protected @Nullable VibrationEffect getErrorVibrationEffect() { - if (!mCustomHaptics) { - return super.getErrorVibrationEffect(); - } - - if (Settings.Global.getInt(mContentResolver, "fp_error_enabled", 1) == 0) { - return null; - } - - return getVibration(Settings.Global.getString(mContentResolver, - "fp_error_type"), super.getErrorVibrationEffect()); - - } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 98f9fe178b9be..db927b227d9a9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -33,7 +33,6 @@ import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; -import android.os.VibrationEffect; import android.provider.Settings; import android.util.Slog; @@ -264,22 +263,16 @@ class FaceAuthenticationClient extends AuthenticationClient implements } @Override - protected @Nullable VibrationEffect getSuccessVibrationEffect() { - if (!mCustomHaptics) { - return super.getSuccessVibrationEffect(); - } - - return getVibration(Settings.Global.getString(mContentResolver, - "face_success_type"), super.getSuccessVibrationEffect()); + protected boolean successHapticsEnabled() { + return mCustomHaptics + ? Settings.Global.getInt(mContentResolver, "face_success_enabled", 1) == 0 + : super.successHapticsEnabled(); } @Override - protected @Nullable VibrationEffect getErrorVibrationEffect() { - if (!mCustomHaptics) { - return super.getErrorVibrationEffect(); - } - - return getVibration(Settings.Global.getString(mContentResolver, - "face_error_type"), super.getErrorVibrationEffect()); + protected boolean errorHapticsEnabled() { + return mCustomHaptics + ? Settings.Global.getInt(mContentResolver, "face_error_enabled", 1) == 0 + : super.errorHapticsEnabled(); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 38e6f083da514..6c0adafcf2ee2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -28,7 +28,6 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.FaceManager; import android.os.IBinder; import android.os.RemoteException; -import android.os.VibrationEffect; import android.provider.Settings; import android.util.Slog; @@ -203,22 +202,16 @@ class FaceAuthenticationClient extends AuthenticationClient { } @Override - protected @NonNull VibrationEffect getSuccessVibrationEffect() { - if (!mCustomHaptics) { - return super.getSuccessVibrationEffect(); - } - - return getVibration(Settings.Global.getString(mContentResolver, - "face_success_type"), super.getSuccessVibrationEffect()); + protected boolean successHapticsEnabled() { + return mCustomHaptics + ? Settings.Global.getInt(mContentResolver, "face_success_enabled", 1) == 0 + : super.successHapticsEnabled(); } @Override - protected @NonNull VibrationEffect getErrorVibrationEffect() { - if (!mCustomHaptics) { - return super.getErrorVibrationEffect(); - } - - return getVibration(Settings.Global.getString(mContentResolver, - "face_error_type"), super.getErrorVibrationEffect()); + protected boolean errorHapticsEnabled() { + return mCustomHaptics + ? Settings.Global.getInt(mContentResolver, "face_error_enabled", 1) == 0 + : super.errorHapticsEnabled(); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index ba6ef29554616..1825eda1375a2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskStackListener; +import android.content.ContentResolver; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; @@ -29,6 +30,7 @@ import android.hardware.biometrics.fingerprint.ISession; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; +import android.provider.Settings; import android.util.Slog; import com.android.server.biometrics.Utils; @@ -55,6 +57,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp @Nullable private final IUdfpsOverlayController mUdfpsOverlayController; @Nullable private ICancellationSignal mCancellationSignal; + @NonNull private final ContentResolver mContentResolver; + private final boolean mCustomHaptics; + FingerprintAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, @@ -69,6 +74,10 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp lockoutCache, allowBackgroundAuthentication); mLockoutCache = lockoutCache; mUdfpsOverlayController = udfpsOverlayController; + + mContentResolver = context.getContentResolver(); + mCustomHaptics = Settings.Global.getInt(mContentResolver, + "fp_custom_success_error", 0) == 1; } @NonNull @@ -204,4 +213,18 @@ class FingerprintAuthenticationClient extends AuthenticationClient imp UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController); mCallback.onClientFinished(this, false /* success */); } + + @Override + protected boolean successHapticsEnabled() { + return mCustomHaptics + ? Settings.Global.getInt(mContentResolver, "fp_success_enabled", 1) == 0 + : super.successHapticsEnabled(); + } + + @Override + protected boolean errorHapticsEnabled() { + return mCustomHaptics + ? Settings.Global.getInt(mContentResolver, "fp_error_enabled", 1) == 0 + : super.errorHapticsEnabled(); + } }