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

Commit 03a6282e authored by Joe Bolinger's avatar Joe Bolinger Committed by Android (Google) Code Review
Browse files

Merge "Update session log changes to include new context information."

parents baccd601 cc31cf9f
Loading
Loading
Loading
Loading
+13 −1
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ package android.hardware.biometrics;

/**
 * A secondary communication channel from AuthController back to BiometricService for
 * events that are not associated with an autentication session. See
 * events that are not associated with an authentication session. See
 * {@link IBiometricSysuiReceiver} for events associated with a session.
 *
 * @hide
@@ -27,4 +27,16 @@ oneway interface IBiometricContextListener {
    // These may be called while the device is still transitioning to the new state
    // (i.e. about to become awake or enter doze)
    void onDozeChanged(boolean isDozing, boolean isAwake);

    @VintfStability
    @Backing(type="int")
    enum FoldState {
        UNKNOWN = 0,
        HALF_OPENED = 1,
        FULLY_OPENED = 2,
        FULLY_CLOSED = 3,
    }

    // Called when the fold state of the device changes.
    void onFoldChanged(FoldState FoldState);
}
+5 −42
Original line number Diff line number Diff line
@@ -20,9 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR;

import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -74,6 +71,7 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -81,7 +79,6 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.data.repository.BiometricType;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -121,7 +118,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
    private final Context mContext;
    private final Execution mExecution;
    private final CommandQueue mCommandQueue;
    private final StatusBarStateController mStatusBarStateController;
    private final ActivityTaskManager mActivityTaskManager;
    @Nullable private final FingerprintManager mFingerprintManager;
    @Nullable private final FaceManager mFaceManager;
@@ -131,6 +127,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
    // TODO: these should be migrated out once ready
    @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
    @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
    @NonNull private final LogContextInteractor mLogContextInteractor;

    private final Display mDisplay;
    private float mScaleFactor = 1f;
@@ -153,7 +150,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
    @Nullable private UdfpsController mUdfpsController;
    @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
    @Nullable private SideFpsController mSideFpsController;
    @Nullable private IBiometricContextListener mBiometricContextListener;
    @Nullable private UdfpsLogger mUdfpsLogger;
    @VisibleForTesting IBiometricSysuiReceiver mReceiver;
    @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@@ -728,7 +724,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
            @NonNull UserManager userManager,
            @NonNull LockPatternUtils lockPatternUtils,
            @NonNull UdfpsLogger udfpsLogger,
            @NonNull StatusBarStateController statusBarStateController,
            @NonNull LogContextInteractor logContextInteractor,
            @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
            @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
            @NonNull InteractionJankMonitor jankMonitor,
@@ -756,6 +752,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
        mFaceEnrolledForUser = new SparseBooleanArray();
        mVibratorHelper = vibrator;

        mLogContextInteractor = logContextInteractor;
        mBiometricPromptInteractor = biometricPromptInteractor;
        mCredentialViewModelProvider = credentialViewModelProvider;

@@ -770,25 +767,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
                });

        mWakefulnessLifecycle = wakefulnessLifecycle;
        mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
            @Override
            public void onFinishedWakingUp() {
                notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_AWAKE);
            }

            @Override
            public void onStartedGoingToSleep() {
                notifyDozeChanged(mStatusBarStateController.isDozing(), WAKEFULNESS_GOING_TO_SLEEP);
            }
        });

        mStatusBarStateController = statusBarStateController;
        mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() {
            @Override
            public void onDozingChanged(boolean isDozing) {
                notifyDozeChanged(isDozing, wakefulnessLifecycle.getWakefulness());
            }
        });

        mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
        int[] faceAuthLocation = context.getResources().getIntArray(
@@ -894,21 +872,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,

    @Override
    public void setBiometricContextListener(IBiometricContextListener listener) {
        mBiometricContextListener = listener;
        notifyDozeChanged(mStatusBarStateController.isDozing(),
                mWakefulnessLifecycle.getWakefulness());
    }

    private void notifyDozeChanged(boolean isDozing,
            @WakefulnessLifecycle.Wakefulness int wakefullness) {
        if (mBiometricContextListener != null) {
            try {
                final boolean isAwake = wakefullness == WAKEFULNESS_AWAKE;
                mBiometricContextListener.onDozeChanged(isDozing, isAwake);
            } catch (RemoteException e) {
                Log.w(TAG, "failed to notify initial doze state");
            }
        }
        mLogContextInteractor.addBiometricContextListener(listener);
    }

    /**
@@ -1274,7 +1238,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
            PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds,
            String opPackageName, boolean skipIntro, long operationId, long requestId,
            @BiometricMultiSensorMode int multiSensorConfig,

            @NonNull WakefulnessLifecycle wakefulnessLifecycle,
            @NonNull UserManager userManager,
            @NonNull LockPatternUtils lockPatternUtils) {
+6 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.concurrency.ThreadFactory
import dagger.Binds
@@ -40,6 +42,10 @@ interface BiometricsModule {
    @SysUISingleton
    fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor

    @Binds
    @SysUISingleton
    fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor

    companion object {
        /** Background [Executor] for HAL related operations. */
        @Provides
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.domain.interactor

import android.hardware.biometrics.IBiometricContextListener
import android.util.Log
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
import com.android.systemui.unfold.updates.FoldStateProvider
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch

/**
 * Aggregates UI/device state that is not directly related to biometrics, but is often useful for
 * logging or optimization purposes (fold state, screen state, etc.)
 */
interface LogContextInteractor {

    /** If the device is dozing. */
    val isDozing: Flow<Boolean>

    /** If the device is currently awake with the screen on. */
    val isAwake: Flow<Boolean>

    /** Current device fold state, defined as [IBiometricContextListener.FoldState]. */
    val foldState: Flow<Int>

    /**
     * Add a permanent context listener.
     *
     * Use this method for registering remote context listeners. Use the properties exposed via this
     * class directly within SysUI.
     */
    fun addBiometricContextListener(listener: IBiometricContextListener): Job
}

@SysUISingleton
class LogContextInteractorImpl
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    private val statusBarStateController: StatusBarStateController,
    private val wakefulnessLifecycle: WakefulnessLifecycle,
    private val foldProvider: FoldStateProvider,
) : LogContextInteractor {

    init {
        foldProvider.start()
    }

    override val isDozing =
        conflatedCallbackFlow {
                val callback =
                    object : StatusBarStateController.StateListener {
                        override fun onDozingChanged(isDozing: Boolean) {
                            trySendWithFailureLogging(isDozing, TAG)
                        }
                    }

                statusBarStateController.addCallback(callback)
                trySendWithFailureLogging(statusBarStateController.isDozing, TAG)
                awaitClose { statusBarStateController.removeCallback(callback) }
            }
            .distinctUntilChanged()

    override val isAwake =
        conflatedCallbackFlow {
                val callback =
                    object : WakefulnessLifecycle.Observer {
                        override fun onFinishedWakingUp() {
                            trySendWithFailureLogging(true, TAG)
                        }

                        override fun onStartedGoingToSleep() {
                            trySendWithFailureLogging(false, TAG)
                        }
                    }

                wakefulnessLifecycle.addObserver(callback)
                trySendWithFailureLogging(wakefulnessLifecycle.isAwake, TAG)
                awaitClose { wakefulnessLifecycle.removeObserver(callback) }
            }
            .distinctUntilChanged()

    override val foldState: Flow<Int> =
        conflatedCallbackFlow {
                val callback =
                    object : FoldStateProvider.FoldUpdatesListener {
                        override fun onHingeAngleUpdate(angle: Float) {}

                        override fun onFoldUpdate(@FoldStateProvider.FoldUpdate update: Int) {
                            val loggedState =
                                when (update) {
                                    FOLD_UPDATE_FINISH_HALF_OPEN ->
                                        IBiometricContextListener.FoldState.HALF_OPENED
                                    FOLD_UPDATE_FINISH_FULL_OPEN ->
                                        IBiometricContextListener.FoldState.FULLY_OPENED
                                    FOLD_UPDATE_FINISH_CLOSED ->
                                        IBiometricContextListener.FoldState.FULLY_CLOSED
                                    else -> null
                                }
                            if (loggedState != null) {
                                trySendWithFailureLogging(loggedState, TAG)
                            }
                        }
                    }

                foldProvider.addCallback(callback)
                trySendWithFailureLogging(IBiometricContextListener.FoldState.UNKNOWN, TAG)
                awaitClose { foldProvider.removeCallback(callback) }
            }
            .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)

    override fun addBiometricContextListener(listener: IBiometricContextListener): Job {
        return applicationScope.launch {
            combine(isDozing, isAwake) { doze, awake -> doze to awake }
                .onEach { (doze, awake) -> listener.onDozeChanged(doze, awake) }
                .catch { t -> Log.w(TAG, "failed to notify new doze state", t) }
                .launchIn(this)

            foldState
                .onEach { state -> listener.onFoldChanged(state) }
                .catch { t -> Log.w(TAG, "failed to notify new fold state", t) }
                .launchIn(this)

            listener.asBinder().linkToDeath({ cancel() }, 0)
        }
    }

    companion object {
        private const val TAG = "ContextRepositoryImpl"
    }
}

private val WakefulnessLifecycle.isAwake: Boolean
    get() = wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
+14 −76
Original line number Diff line number Diff line
@@ -20,8 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;

import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;

import static com.google.common.truth.Truth.assertThat;

import static junit.framework.Assert.assertEquals;
@@ -35,12 +33,11 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -90,9 +87,9 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -108,7 +105,6 @@ import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -117,8 +113,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.inject.Provider;

@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -162,7 +156,7 @@ public class AuthControllerTest extends SysuiTestCase {
    @Mock
    private LockPatternUtils mLockPatternUtils;
    @Mock
    private StatusBarStateController mStatusBarStateController;
    private LogContextInteractor mLogContextInteractor;
    @Mock
    private UdfpsLogger mUdfpsLogger;
    @Mock
@@ -178,10 +172,6 @@ public class AuthControllerTest extends SysuiTestCase {
    private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
    @Captor
    private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
    @Captor
    private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
    @Captor
    private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefullnessObserverCaptor;

    private TestableContext mContextSpy;
    private Execution mExecution;
@@ -253,10 +243,7 @@ public class AuthControllerTest extends SysuiTestCase {
        when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
        when(mVibratorHelper.hasVibrator()).thenReturn(true);

        mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue,
                mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager,
                () -> mUdfpsController, () -> mSideFpsController, mStatusBarStateController,
                mVibratorHelper);
        mAuthController = new TestableAuthController(mContextSpy);

        mAuthController.start();
        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -264,11 +251,6 @@ public class AuthControllerTest extends SysuiTestCase {
        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
                mFaceAuthenticatorsRegisteredCaptor.capture());

        when(mStatusBarStateController.isDozing()).thenReturn(false);
        when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
        verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
        verify(mWakefulnessLifecycle).addObserver(mWakefullnessObserverCaptor.capture());

        mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
        mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);

@@ -286,10 +268,7 @@ public class AuthControllerTest extends SysuiTestCase {
        reset(mFaceManager);

        // This test requires an uninitialized AuthController.
        AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
                mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
                mStatusBarStateController, mVibratorHelper);
        AuthController authController = new TestableAuthController(mContextSpy);
        authController.start();

        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -316,10 +295,7 @@ public class AuthControllerTest extends SysuiTestCase {
        reset(mFaceManager);

        // This test requires an uninitialized AuthController.
        AuthController authController = new TestableAuthController(mContextSpy, mExecution,
                mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
                mFaceManager, () -> mUdfpsController, () -> mSideFpsController,
                mStatusBarStateController, mVibratorHelper);
        AuthController authController = new TestableAuthController(mContextSpy);
        authController.start();

        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
@@ -809,37 +785,9 @@ public class AuthControllerTest extends SysuiTestCase {
    }

    @Test
    public void testForwardsDozeEvents() throws RemoteException {
        when(mStatusBarStateController.isDozing()).thenReturn(true);
        when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
        mAuthController.setBiometricContextListener(mContextListener);

        mStatusBarStateListenerCaptor.getValue().onDozingChanged(true);
        mStatusBarStateListenerCaptor.getValue().onDozingChanged(false);

        InOrder order = inOrder(mContextListener);
        order.verify(mContextListener, times(2)).onDozeChanged(eq(true), eq(true));
        order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
        order.verifyNoMoreInteractions();
    }

    @Test
    public void testForwardsWakeEvents() throws RemoteException {
        when(mStatusBarStateController.isDozing()).thenReturn(false);
        when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
    public void testSubscribesToLogContext() {
        mAuthController.setBiometricContextListener(mContextListener);

        mWakefullnessObserverCaptor.getValue().onStartedGoingToSleep();
        mWakefullnessObserverCaptor.getValue().onFinishedGoingToSleep();
        mWakefullnessObserverCaptor.getValue().onStartedWakingUp();
        mWakefullnessObserverCaptor.getValue().onFinishedWakingUp();
        mWakefullnessObserverCaptor.getValue().onPostFinishedWakingUp();

        InOrder order = inOrder(mContextListener);
        order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
        order.verify(mContextListener).onDozeChanged(eq(false), eq(false));
        order.verify(mContextListener).onDozeChanged(eq(false), eq(true));
        order.verifyNoMoreInteractions();
        verify(mLogContextInteractor).addBiometricContextListener(same(mContextListener));
    }

    @Test
@@ -1001,23 +949,13 @@ public class AuthControllerTest extends SysuiTestCase {
        private int mBuildCount = 0;
        private PromptInfo mLastBiometricPromptInfo;

        TestableAuthController(Context context,
                Execution execution,
                CommandQueue commandQueue,
                ActivityTaskManager activityTaskManager,
                WindowManager windowManager,
                FingerprintManager fingerprintManager,
                FaceManager faceManager,
                Provider<UdfpsController> udfpsControllerFactory,
                Provider<SideFpsController> sidefpsControllerFactory,
                StatusBarStateController statusBarStateController,
                VibratorHelper vibratorHelper) {
            super(context, execution, commandQueue, activityTaskManager, windowManager,
                    fingerprintManager, faceManager, udfpsControllerFactory,
                    sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
                    mUserManager, mLockPatternUtils, mUdfpsLogger, statusBarStateController,
        TestableAuthController(Context context) {
            super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
                    mFingerprintManager, mFaceManager, () -> mUdfpsController,
                    () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
                    mUserManager, mLockPatternUtils, mUdfpsLogger, mLogContextInteractor,
                    () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper);
                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper);
        }

        @Override
Loading