Loading packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +2 −0 Original line number Diff line number Diff line package com.android.systemui.deviceentry import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import dagger.Module Loading @@ -7,6 +8,7 @@ import dagger.Module includes = [ DeviceEntryRepositoryModule::class, DeviceEntryHapticsRepositoryModule::class, ], ) object DeviceEntryModule packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.deviceentry.data.repository import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow /** Interface for classes that can access device-entry haptics application state. */ interface DeviceEntryHapticsRepository { /** * Whether a successful biometric haptic has been requested. Has not yet been handled if true. */ val successHapticRequest: Flow<Boolean> /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ val errorHapticRequest: Flow<Boolean> fun requestSuccessHaptic() fun handleSuccessHaptic() fun requestErrorHaptic() fun handleErrorHaptic() } /** Encapsulates application state for device entry haptics. */ @SysUISingleton class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { private val _successHapticRequest = MutableStateFlow(false) override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() private val _errorHapticRequest = MutableStateFlow(false) override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() override fun requestSuccessHaptic() { _successHapticRequest.value = true } override fun handleSuccessHaptic() { _successHapticRequest.value = false } override fun requestErrorHaptic() { _errorHapticRequest.value = true } override fun handleErrorHaptic() { _errorHapticRequest.value = false } } @Module interface DeviceEntryHapticsRepositoryModule { @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository } packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.deviceentry.domain.interactor import com.android.keyguard.logging.BiometricUnlockLogger import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.util.kotlin.sample import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart /** * Business logic for device entry haptic events. Determines whether the haptic should play. In * particular, there are extra guards for whether device entry error and successes hatpics should * play when the physical fingerprint sensor is located on the power button. */ @ExperimentalCoroutinesApi @SysUISingleton class DeviceEntryHapticsInteractor @Inject constructor( private val repository: DeviceEntryHapticsRepository, fingerprintPropertyRepository: FingerprintPropertyRepository, biometricSettingsRepository: BiometricSettingsRepository, keyEventInteractor: KeyEventInteractor, powerInteractor: PowerInteractor, private val systemClock: SystemClock, private val logger: BiometricUnlockLogger, ) { private val powerButtonSideFpsEnrolled = combineTransform( fingerprintPropertyRepository.sensorType, biometricSettingsRepository.isFingerprintEnrolledAndEnabled, ) { sensorType, enrolledAndEnabled -> if (sensorType == FingerprintSensorType.POWER_BUTTON) { emit(enrolledAndEnabled) } else { emit(false) } } .distinctUntilChanged() private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown private val lastPowerButtonWakeup: Flow<Long> = powerInteractor.detailedWakefulness .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } .map { systemClock.uptimeMillis() } .onStart { // If the power button hasn't been pressed, we still want this to evaluate to true: // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` emit(recentPowerButtonPressThresholdMs * -1L - 1L) } val playSuccessHaptic: Flow<Boolean> = repository.successHapticRequest .filter { it } .sample( combine( powerButtonSideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup, ::Triple ) ) .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> val sideFpsAllowsHaptic = !powerButtonDown && systemClock.uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic if (!allowHaptic) { logger.d("Skip success haptic. Recent power button press or button is down.") handleSuccessHaptic() // immediately handle, don't vibrate } allowHaptic } val playErrorHaptic: Flow<Boolean> = repository.errorHapticRequest .filter { it } .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) .map { (sideFpsEnrolled, powerButtonDown) -> val allowHaptic = !sideFpsEnrolled || !powerButtonDown if (!allowHaptic) { logger.d("Skip error haptic. Power button is down.") handleErrorHaptic() // immediately handle, don't vibrate } allowHaptic } fun vibrateSuccess() { repository.requestSuccessHaptic() } fun vibrateError() { repository.requestErrorHaptic() } fun handleSuccessHaptic() { repository.handleSuccessHaptic() } fun handleErrorHaptic() { repository.handleErrorHaptic() } private val recentPowerButtonPressThresholdMs = 400L } packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +7 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder Loading @@ -44,6 +45,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import javax.inject.Inject Loading Loading @@ -72,7 +74,9 @@ constructor( private val keyguardIndicationController: KeyguardIndicationController, private val lockIconViewController: LockIconViewController, private val shadeInteractor: ShadeInteractor, private val interactionJankMonitor: InteractionJankMonitor private val interactionJankMonitor: InteractionJankMonitor, private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, private val vibratorHelper: VibratorHelper, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null Loading Loading @@ -143,6 +147,8 @@ constructor( shadeInteractor, { keyguardStatusViewController!!.getClockController() }, interactionJankMonitor, deviceEntryHapticsInteractor, vibratorHelper, ) } Loading packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +44 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.DrawableRes import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup Loading @@ -29,6 +30,7 @@ import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.TransitionState Loading @@ -38,6 +40,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator Loading @@ -45,6 +48,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ Loading @@ -62,6 +66,8 @@ object KeyguardRootViewBinder { shadeInteractor: ShadeInteractor, clockControllerProvider: Provider<ClockController>?, interactionJankMonitor: InteractionJankMonitor?, deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, vibratorHelper: VibratorHelper?, ): DisposableHandle { var onLayoutChangeListener: OnLayoutChange? = null val childViews = mutableMapOf<Int, View?>() Loading Loading @@ -177,6 +183,44 @@ object KeyguardRootViewBinder { } } } if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { deviceEntryHapticsInteractor.playSuccessHaptic .filter { it } .collect { if ( featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) ) { vibratorHelper.performHapticFeedback( view, HapticFeedbackConstants.CONFIRM, ) } else { vibratorHelper.vibrateAuthSuccess("device-entry::success") } deviceEntryHapticsInteractor.handleSuccessHaptic() } } launch { deviceEntryHapticsInteractor.playErrorHaptic .filter { it } .collect { if ( featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) ) { vibratorHelper.performHapticFeedback( view, HapticFeedbackConstants.REJECT, ) } else { vibratorHelper.vibrateAuthSuccess("device-entry::error") } deviceEntryHapticsInteractor.handleErrorHaptic() } } } } } viewModel.clockControllerProvider = clockControllerProvider Loading Loading
packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +2 −0 Original line number Diff line number Diff line package com.android.systemui.deviceentry import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import dagger.Module Loading @@ -7,6 +8,7 @@ import dagger.Module includes = [ DeviceEntryRepositoryModule::class, DeviceEntryHapticsRepositoryModule::class, ], ) object DeviceEntryModule
packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.deviceentry.data.repository import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow /** Interface for classes that can access device-entry haptics application state. */ interface DeviceEntryHapticsRepository { /** * Whether a successful biometric haptic has been requested. Has not yet been handled if true. */ val successHapticRequest: Flow<Boolean> /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ val errorHapticRequest: Flow<Boolean> fun requestSuccessHaptic() fun handleSuccessHaptic() fun requestErrorHaptic() fun handleErrorHaptic() } /** Encapsulates application state for device entry haptics. */ @SysUISingleton class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { private val _successHapticRequest = MutableStateFlow(false) override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() private val _errorHapticRequest = MutableStateFlow(false) override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() override fun requestSuccessHaptic() { _successHapticRequest.value = true } override fun handleSuccessHaptic() { _successHapticRequest.value = false } override fun requestErrorHaptic() { _errorHapticRequest.value = true } override fun handleErrorHaptic() { _errorHapticRequest.value = false } } @Module interface DeviceEntryHapticsRepositoryModule { @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository }
packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.deviceentry.domain.interactor import com.android.keyguard.logging.BiometricUnlockLogger import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.util.kotlin.sample import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart /** * Business logic for device entry haptic events. Determines whether the haptic should play. In * particular, there are extra guards for whether device entry error and successes hatpics should * play when the physical fingerprint sensor is located on the power button. */ @ExperimentalCoroutinesApi @SysUISingleton class DeviceEntryHapticsInteractor @Inject constructor( private val repository: DeviceEntryHapticsRepository, fingerprintPropertyRepository: FingerprintPropertyRepository, biometricSettingsRepository: BiometricSettingsRepository, keyEventInteractor: KeyEventInteractor, powerInteractor: PowerInteractor, private val systemClock: SystemClock, private val logger: BiometricUnlockLogger, ) { private val powerButtonSideFpsEnrolled = combineTransform( fingerprintPropertyRepository.sensorType, biometricSettingsRepository.isFingerprintEnrolledAndEnabled, ) { sensorType, enrolledAndEnabled -> if (sensorType == FingerprintSensorType.POWER_BUTTON) { emit(enrolledAndEnabled) } else { emit(false) } } .distinctUntilChanged() private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown private val lastPowerButtonWakeup: Flow<Long> = powerInteractor.detailedWakefulness .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } .map { systemClock.uptimeMillis() } .onStart { // If the power button hasn't been pressed, we still want this to evaluate to true: // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` emit(recentPowerButtonPressThresholdMs * -1L - 1L) } val playSuccessHaptic: Flow<Boolean> = repository.successHapticRequest .filter { it } .sample( combine( powerButtonSideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup, ::Triple ) ) .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> val sideFpsAllowsHaptic = !powerButtonDown && systemClock.uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic if (!allowHaptic) { logger.d("Skip success haptic. Recent power button press or button is down.") handleSuccessHaptic() // immediately handle, don't vibrate } allowHaptic } val playErrorHaptic: Flow<Boolean> = repository.errorHapticRequest .filter { it } .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) .map { (sideFpsEnrolled, powerButtonDown) -> val allowHaptic = !sideFpsEnrolled || !powerButtonDown if (!allowHaptic) { logger.d("Skip error haptic. Power button is down.") handleErrorHaptic() // immediately handle, don't vibrate } allowHaptic } fun vibrateSuccess() { repository.requestSuccessHaptic() } fun vibrateError() { repository.requestErrorHaptic() } fun handleSuccessHaptic() { repository.handleSuccessHaptic() } fun handleErrorHaptic() { repository.handleErrorHaptic() } private val recentPowerButtonPressThresholdMs = 400L }
packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +7 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder Loading @@ -44,6 +45,7 @@ import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import javax.inject.Inject Loading Loading @@ -72,7 +74,9 @@ constructor( private val keyguardIndicationController: KeyguardIndicationController, private val lockIconViewController: LockIconViewController, private val shadeInteractor: ShadeInteractor, private val interactionJankMonitor: InteractionJankMonitor private val interactionJankMonitor: InteractionJankMonitor, private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, private val vibratorHelper: VibratorHelper, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null Loading Loading @@ -143,6 +147,8 @@ constructor( shadeInteractor, { keyguardStatusViewController!!.getClockController() }, interactionJankMonitor, deviceEntryHapticsInteractor, vibratorHelper, ) } Loading
packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +44 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.DrawableRes import android.view.HapticFeedbackConstants import android.view.View import android.view.View.OnLayoutChangeListener import android.view.ViewGroup Loading @@ -29,6 +30,7 @@ import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.TransitionState Loading @@ -38,6 +40,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ClockController import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator Loading @@ -45,6 +48,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */ Loading @@ -62,6 +66,8 @@ object KeyguardRootViewBinder { shadeInteractor: ShadeInteractor, clockControllerProvider: Provider<ClockController>?, interactionJankMonitor: InteractionJankMonitor?, deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, vibratorHelper: VibratorHelper?, ): DisposableHandle { var onLayoutChangeListener: OnLayoutChange? = null val childViews = mutableMapOf<Int, View?>() Loading Loading @@ -177,6 +183,44 @@ object KeyguardRootViewBinder { } } } if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { deviceEntryHapticsInteractor.playSuccessHaptic .filter { it } .collect { if ( featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) ) { vibratorHelper.performHapticFeedback( view, HapticFeedbackConstants.CONFIRM, ) } else { vibratorHelper.vibrateAuthSuccess("device-entry::success") } deviceEntryHapticsInteractor.handleSuccessHaptic() } } launch { deviceEntryHapticsInteractor.playErrorHaptic .filter { it } .collect { if ( featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) ) { vibratorHelper.performHapticFeedback( view, HapticFeedbackConstants.REJECT, ) } else { vibratorHelper.vibrateAuthSuccess("device-entry::error") } deviceEntryHapticsInteractor.handleErrorHaptic() } } } } } viewModel.clockControllerProvider = clockControllerProvider Loading