Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b796ff7a authored by Beverly's avatar Beverly
Browse files

Add adb commands to test udfps haptics

- Remove ability to customize biometric unlock haptic for face and
fingerprint via an adb command (unneeded) - UX only wants to adjust
whether to enable face/fingerprint haptics individually
- Add adb commands to trigger udfps haptics on <command>:
    * start (depends on configuration)
    * acquired (depends on configuration)
    * success (will always play CLICK haptic)
    * error (will always play DOUBLE_CLICK haptic)
  Usage: adb shell cmd statusbar udfps-haptic <command>

Test: manual, atest SystemUITest, atest UdfpsController
Bug: 185124905
Change-Id: Iba6c9f95302c5638957073733cc8dfd366f8984d
parent 99a680a5
Loading
Loading
Loading
Loading
+19 −14
Original line number Diff line number Diff line
@@ -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,10 +1404,14 @@ 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;
    /**
     * 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");
@@ -1414,7 +1420,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
                    UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
        }
    }
            };

    private final FaceManager.FaceDetectionCallback mFaceDetectionCallback
            = (sensorId, userId, isStrongBiometric) -> {
+25 −16
Original line number Diff line number Diff line
@@ -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<UdfpsHbmProvider> 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)
+94 −0
Original line number Diff line number Diff line
/*
 * 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<String>) {
        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 <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
+3 −0
Original line number Diff line number Diff line
@@ -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();
+18 −46
Original line number Diff line number Diff line
@@ -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<T> extends HalClientMonitor<T> 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<T> extends HalClientMonitor<T> 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<T> extends HalClientMonitor<T> 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);
        }
    }
}
Loading