Add ability to customize haptics on auth success/fail

Temporary change for UX:
With adb commands, you can customize haptic feedback for fingerprint and
face authentication fail / success.

Test: manual, atest BiometricsSchedulerTest
Bug: 185124905
Change-Id: If2c1a23095a74edc40409ad2bfc35ca704852e19
This commit is contained in:
Beverly
2021-04-22 16:03:06 -04:00
parent f98e690f9e
commit 7770028a62
6 changed files with 143 additions and 7 deletions

View File

@@ -143,6 +143,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
private final VibrationEffect mEffectHeavy =
VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
private final VibrationEffect mDoubleClick =
VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
private final Runnable mAcquiredVibration = new Runnable() {
@Override
public void run() {
@@ -714,6 +716,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
return mEffectTextureTick;
case "tick":
return mEffectTick;
case "double_tap":
return mDoubleClick;
default:
return defaultEffect;
}

View File

@@ -17,6 +17,7 @@
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;
@@ -26,6 +27,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.text.TextUtils;
import android.util.Slog;
/**
@@ -43,6 +45,15 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
.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 =
VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
private final PowerManager mPowerManager;
private final VibrationEffect mSuccessVibrationEffect;
private final VibrationEffect mErrorVibrationEffect;
@@ -61,8 +72,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality,
statsAction, statsClient);
mPowerManager = context.getSystemService(PowerManager.class);
mSuccessVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
mErrorVibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
mSuccessVibrationEffect = mEffectClick;
mErrorVibrationEffect = mDoubleClick;
}
@Override
@@ -181,18 +192,47 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement
mPowerManager.userActivity(now, PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
}
protected @NonNull VibrationEffect getSuccessVibrationEffect() {
return mSuccessVibrationEffect;
}
protected @NonNull VibrationEffect getErrorVibrationEffect() {
return mErrorVibrationEffect;
}
protected final void vibrateSuccess() {
Vibrator vibrator = getContext().getSystemService(Vibrator.class);
if (vibrator != null) {
vibrator.vibrate(mSuccessVibrationEffect, VIBRATION_SONFICATION_ATTRIBUTES);
vibrator.vibrate(getSuccessVibrationEffect(), VIBRATION_SONFICATION_ATTRIBUTES);
}
}
protected final void vibrateError() {
Vibrator vibrator = getContext().getSystemService(Vibrator.class);
if (vibrator != null) {
vibrator.vibrate(mErrorVibrationEffect, VIBRATION_SONFICATION_ATTRIBUTES);
vibrator.vibrate(getErrorVibrationEffect(), 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;
}
}
}

View File

@@ -22,6 +22,7 @@ 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;
@@ -30,6 +31,8 @@ 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;
@@ -56,12 +59,14 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
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<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -80,6 +85,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
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) {
@@ -333,4 +342,25 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
public boolean interruptsPrecedingClients() {
return true;
}
@Override
protected @NonNull VibrationEffect getSuccessVibrationEffect() {
if (!mCustomHaptics) {
return super.getSuccessVibrationEffect();
}
return getVibration(Settings.Global.getString(mContentResolver,
"fp_success_type"), super.getSuccessVibrationEffect());
}
@Override
protected @NonNull VibrationEffect getErrorVibrationEffect() {
if (!mCustomHaptics) {
return super.getErrorVibrationEffect();
}
return getVibration(Settings.Global.getString(mContentResolver,
"fp_error_type"), super.getErrorVibrationEffect());
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.aidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -32,6 +33,8 @@ 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;
import com.android.internal.R;
@@ -57,6 +60,9 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@Nullable private final NotificationManager mNotificationManager;
@Nullable private ICancellationSignal mCancellationSignal;
@NonNull private final ContentResolver mContentResolver;
private final boolean mCustomHaptics;
private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor;
private final int[] mKeyguardIgnoreList;
@@ -87,6 +93,10 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
R.array.config_face_acquire_keyguard_ignorelist);
mKeyguardIgnoreListVendor = resources.getIntArray(
R.array.config_face_acquire_vendor_keyguard_ignorelist);
mContentResolver = context.getContentResolver();
mCustomHaptics = Settings.Global.getInt(mContentResolver,
"face_custom_success_error", 0) == 1;
}
@Override
@@ -243,4 +253,24 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
Slog.e(TAG, "Remote exception", e);
}
}
@Override
protected @NonNull VibrationEffect getSuccessVibrationEffect() {
if (!mCustomHaptics) {
return super.getSuccessVibrationEffect();
}
return getVibration(Settings.Global.getString(mContentResolver,
"face_success_type"), super.getSuccessVibrationEffect());
}
@Override
protected @NonNull VibrationEffect getErrorVibrationEffect() {
if (!mCustomHaptics) {
return super.getErrorVibrationEffect();
}
return getVibration(Settings.Global.getString(mContentResolver,
"face_error_type"), super.getErrorVibrationEffect());
}
}

View File

@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.face.hidl;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricAuthenticator;
@@ -27,6 +28,8 @@ 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;
import com.android.internal.R;
@@ -47,7 +50,8 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
private static final String TAG = "FaceAuthenticationClient";
@NonNull private final ContentResolver mContentResolver;
private final boolean mCustomHaptics;
private final UsageStats mUsageStats;
private final int[] mBiometricPromptIgnoreList;
@@ -78,6 +82,10 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
R.array.config_face_acquire_keyguard_ignorelist);
mKeyguardIgnoreListVendor = resources.getIntArray(
R.array.config_face_acquire_vendor_keyguard_ignorelist);
mContentResolver = context.getContentResolver();
mCustomHaptics = Settings.Global.getInt(mContentResolver,
"face_custom_success_error", 0) == 1;
}
@Override
@@ -188,4 +196,24 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
}
@Override
protected @NonNull VibrationEffect getSuccessVibrationEffect() {
if (!mCustomHaptics) {
return super.getSuccessVibrationEffect();
}
return getVibration(Settings.Global.getString(mContentResolver,
"face_success_type"), super.getSuccessVibrationEffect());
}
@Override
protected @NonNull VibrationEffect getErrorVibrationEffect() {
if (!mCustomHaptics) {
return super.getErrorVibrationEffect();
}
return getVibration(Settings.Global.getString(mContentResolver,
"face_error_type"), super.getErrorVibrationEffect());
}
}

View File

@@ -37,6 +37,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
@@ -47,6 +48,7 @@ import com.android.server.biometrics.nano.BiometricsProto;
import com.android.server.biometrics.sensors.BiometricScheduler.Operation;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,11 +64,13 @@ public class BiometricSchedulerTest {
private BiometricScheduler mScheduler;
private IBinder mToken;
@Mock
private Context mContext;
@Mock
private IBiometricService mBiometricService;
@Rule
public final TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);