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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user