1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.bouncer.domain.interactor 18 19 import android.content.Context 20 import android.content.res.ColorStateList 21 import android.os.Handler 22 import android.os.Trace 23 import android.util.Log 24 import android.view.View 25 import com.android.keyguard.KeyguardSecurityModel 26 import com.android.keyguard.KeyguardUpdateMonitor 27 import com.android.systemui.DejankUtils 28 import com.android.systemui.Flags 29 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository 30 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants 31 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN 32 import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel 33 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel 34 import com.android.systemui.bouncer.ui.BouncerView 35 import com.android.systemui.classifier.FalsingCollector 36 import com.android.systemui.dagger.SysUISingleton 37 import com.android.systemui.dagger.qualifiers.Application 38 import com.android.systemui.dagger.qualifiers.Main 39 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor 40 import com.android.systemui.keyguard.DismissCallbackRegistry 41 import com.android.systemui.keyguard.data.repository.TrustRepository 42 import com.android.systemui.plugins.ActivityStarter 43 import com.android.systemui.res.R 44 import com.android.systemui.scene.shared.flag.SceneContainerFlag 45 import com.android.systemui.shared.system.SysUiStatsLog 46 import com.android.systemui.statusbar.policy.KeyguardStateController 47 import com.android.systemui.user.domain.interactor.SelectedUserInteractor 48 import javax.inject.Inject 49 import kotlinx.coroutines.CoroutineScope 50 import kotlinx.coroutines.flow.Flow 51 import kotlinx.coroutines.flow.StateFlow 52 import kotlinx.coroutines.flow.combine 53 import kotlinx.coroutines.flow.filter 54 import kotlinx.coroutines.flow.filterNotNull 55 import kotlinx.coroutines.flow.map 56 import kotlinx.coroutines.launch 57 58 /** 59 * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password) 60 * bouncer. 61 */ 62 @SysUISingleton 63 class PrimaryBouncerInteractor 64 @Inject 65 constructor( 66 private val repository: KeyguardBouncerRepository, 67 private val primaryBouncerView: BouncerView, 68 @Main private val mainHandler: Handler, 69 private val keyguardStateController: KeyguardStateController, 70 private val keyguardSecurityModel: KeyguardSecurityModel, 71 private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor, 72 private val falsingCollector: FalsingCollector, 73 private val dismissCallbackRegistry: DismissCallbackRegistry, 74 private val context: Context, 75 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 76 private val trustRepository: TrustRepository, 77 @Application private val applicationScope: CoroutineScope, 78 private val selectedUserInteractor: SelectedUserInteractor, 79 private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, 80 ) { 81 private val passiveAuthBouncerDelay = 82 context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong() 83 84 /** Runnable to show the primary bouncer. */ 85 val showRunnable = Runnable { 86 repository.setPrimaryShow(true) 87 repository.setPrimaryShowingSoon(false) 88 primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE) 89 } 90 val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth 91 val keyguardAuthenticatedBiometrics: Flow<Boolean> = 92 repository.keyguardAuthenticatedBiometrics.filterNotNull() 93 val keyguardAuthenticatedBiometricsHandled: Flow<Unit> = 94 repository.keyguardAuthenticatedBiometrics.filter { it == null }.map {} 95 val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> = 96 repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull() 97 val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow 98 val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {} 99 val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull() 100 val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull() 101 val startingDisappearAnimation: Flow<Runnable> = 102 repository.primaryBouncerStartingDisappearAnimation.filterNotNull() 103 val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it } 104 val keyguardPosition: Flow<Float> = repository.keyguardPosition.filterNotNull() 105 val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount 106 val lastShownSecurityMode: Flow<KeyguardSecurityModel.SecurityMode> = 107 repository.lastShownSecurityMode 108 109 /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */ 110 val bouncerExpansion: Flow<Float> = 111 combine(repository.panelExpansionAmount, repository.primaryBouncerShow) { 112 panelExpansion, 113 primaryBouncerIsShowing -> 114 if (primaryBouncerIsShowing) { 115 1f - panelExpansion 116 } else { 117 0f 118 } 119 } 120 121 /** Allow for interaction when just about fully visible */ 122 val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 } 123 private var currentUserActiveUnlockRunning = false 124 125 init { 126 applicationScope.launch { 127 trustRepository.isCurrentUserActiveUnlockRunning.collect { 128 currentUserActiveUnlockRunning = it 129 } 130 } 131 } 132 133 // TODO(b/243685699): Move isScrimmed logic to data layer. 134 // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. 135 /** Show the bouncer if necessary and set the relevant states. */ 136 @JvmOverloads 137 fun show(isScrimmed: Boolean) { 138 // When the scene container framework is enabled, instead of calling this, call 139 // SceneInteractor#changeScene(Scenes.Bouncer, ...). 140 SceneContainerFlag.assertInLegacyMode() 141 142 if (primaryBouncerView.delegate == null && !Flags.composeBouncer()) { 143 Log.d( 144 TAG, 145 "PrimaryBouncerInteractor#show is being called before the " + 146 "primaryBouncerDelegate is set. Let's exit early so we don't " + 147 "set the wrong primaryBouncer state." 148 ) 149 return 150 } 151 152 // Reset some states as we show the bouncer. 153 repository.setKeyguardAuthenticatedBiometrics(null) 154 repository.setPrimaryStartingToHide(false) 155 156 val resumeBouncer = 157 (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) && 158 needsFullscreenBouncer() 159 160 Trace.beginSection("KeyguardBouncer#show") 161 repository.setPrimaryScrimmed(isScrimmed) 162 if (isScrimmed) { 163 setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) 164 } 165 166 // In this special case, we want to hide the bouncer and show it again. We want to emit 167 // show(true) again so that we can reinflate the new view. 168 if (resumeBouncer) { 169 repository.setPrimaryShow(false) 170 } 171 172 if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) { 173 // Keyguard is done. 174 return 175 } 176 177 repository.setPrimaryShowingSoon(true) 178 if (usePrimaryBouncerPassiveAuthDelay()) { 179 Log.d(TAG, "delay bouncer, passive auth may succeed") 180 mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay) 181 } else { 182 DejankUtils.postAfterTraversal(showRunnable) 183 } 184 keyguardStateController.notifyPrimaryBouncerShowing(true) 185 primaryBouncerCallbackInteractor.dispatchStartingToShow() 186 Trace.endSection() 187 } 188 189 /** Sets the correct bouncer states to hide the bouncer. */ 190 fun hide() { 191 Trace.beginSection("KeyguardBouncer#hide") 192 if (isFullyShowing()) { 193 SysUiStatsLog.write( 194 SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, 195 SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN 196 ) 197 dismissCallbackRegistry.notifyDismissCancelled() 198 } 199 200 repository.setPrimaryStartDisappearAnimation(null) 201 falsingCollector.onBouncerHidden() 202 keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */) 203 cancelShowRunnable() 204 repository.setPrimaryShowingSoon(false) 205 repository.setPrimaryShow(false) 206 repository.setPanelExpansion(EXPANSION_HIDDEN) 207 primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE) 208 Trace.endSection() 209 } 210 211 /** 212 * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f 213 * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the 214 * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show. 215 * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion 216 * of 0f represents the bouncer fully showing. 217 */ 218 fun setPanelExpansion(expansion: Float) { 219 val oldExpansion = repository.panelExpansionAmount.value 220 val expansionChanged = oldExpansion != expansion 221 if (repository.primaryBouncerStartingDisappearAnimation.value == null) { 222 repository.setPanelExpansion(expansion) 223 } 224 225 if ( 226 expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE && 227 oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE 228 ) { 229 falsingCollector.onBouncerShown() 230 primaryBouncerCallbackInteractor.dispatchFullyShown() 231 } else if ( 232 expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN && 233 oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN 234 ) { 235 /* 236 * There are cases where #hide() was not invoked, such as when 237 * NotificationPanelViewController controls the hide animation. Make sure the state gets 238 * updated by calling #hide() directly. 239 */ 240 hide() 241 DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() } 242 primaryBouncerCallbackInteractor.dispatchFullyHidden() 243 } else if ( 244 expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE && 245 oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE 246 ) { 247 primaryBouncerCallbackInteractor.dispatchStartingToHide() 248 repository.setPrimaryStartingToHide(true) 249 } 250 if (expansionChanged) { 251 primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion) 252 } 253 } 254 255 /** Set the initial keyguard message to show when bouncer is shown. */ 256 fun showMessage(message: String?, colorStateList: ColorStateList?) { 257 repository.setShowMessage(BouncerShowMessageModel(message, colorStateList)) 258 } 259 260 val bouncerDismissAction: BouncerDismissActionModel? 261 get() = repository.bouncerDismissActionModel 262 263 /** 264 * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is 265 * unlocked, we will run the onDismissAction. If the bouncer is exited before unlocking, we call 266 * cancelAction. 267 */ 268 fun setDismissAction( 269 onDismissAction: ActivityStarter.OnDismissAction?, 270 cancelAction: Runnable? 271 ) { 272 repository.bouncerDismissActionModel = 273 if (onDismissAction != null && cancelAction != null) { 274 BouncerDismissActionModel(onDismissAction, cancelAction) 275 } else { 276 null 277 } 278 primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction) 279 } 280 281 /** Update the resources of the views. */ 282 fun updateResources() { 283 repository.setResourceUpdateRequests(true) 284 } 285 286 /** Tell the bouncer that keyguard is authenticated with primary authentication. */ 287 fun notifyKeyguardAuthenticatedPrimaryAuth(userId: Int) { 288 applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) } 289 } 290 291 /** Tell the bouncer that bouncer is requested when device is already authenticated */ 292 fun notifyUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) { 293 applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) } 294 } 295 296 /** Tell the bouncer that keyguard is authenticated with biometrics. */ 297 fun notifyKeyguardAuthenticatedBiometrics(strongAuth: Boolean) { 298 repository.setKeyguardAuthenticatedBiometrics(strongAuth) 299 } 300 301 /** Update the position of the bouncer when showing. */ 302 fun setKeyguardPosition(position: Float) { 303 repository.setKeyguardPosition(position) 304 } 305 306 /** Notifies that the state change was handled. */ 307 fun notifyKeyguardAuthenticatedHandled() { 308 repository.setKeyguardAuthenticatedBiometrics(null) 309 } 310 311 /** Notifies that the message was shown. */ 312 fun onMessageShown() { 313 repository.setShowMessage(null) 314 } 315 316 /** Notify that the resources have been updated */ 317 fun notifyUpdatedResources() { 318 repository.setResourceUpdateRequests(false) 319 } 320 321 /** Set whether back button is enabled when on the bouncer screen. */ 322 fun setBackButtonEnabled(enabled: Boolean) { 323 repository.setIsBackButtonEnabled(enabled) 324 } 325 326 /** Tell the bouncer to start the pre hide animation. */ 327 fun startDisappearAnimation(runnable: Runnable) { 328 if (willRunDismissFromKeyguard()) { 329 runnable.run() 330 return 331 } 332 333 repository.setPrimaryStartDisappearAnimation(runnable) 334 } 335 336 /** Returns whether bouncer is fully showing. */ 337 fun isFullyShowing(): Boolean { 338 return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) && 339 repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE && 340 repository.primaryBouncerStartingDisappearAnimation.value == null 341 } 342 343 /** Returns whether bouncer is scrimmed. */ 344 fun isScrimmed(): Boolean { 345 return repository.primaryBouncerScrimmed.value 346 } 347 348 /** If bouncer expansion is between 0f and 1f non-inclusive. */ 349 fun isInTransit(): Boolean { 350 return repository.primaryBouncerShowingSoon.value || 351 repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN && 352 repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE 353 } 354 355 /** Return whether bouncer is animating away. */ 356 fun isAnimatingAway(): Boolean { 357 return repository.primaryBouncerStartingDisappearAnimation.value != null 358 } 359 360 /** Return whether bouncer will dismiss with actions */ 361 fun willDismissWithAction(): Boolean { 362 return primaryBouncerView.delegate?.willDismissWithActions() == true 363 } 364 365 /** Will the dismissal run from the keyguard layout (instead of from bouncer) */ 366 fun willRunDismissFromKeyguard(): Boolean { 367 return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true 368 } 369 370 /** Returns whether the bouncer should be full screen. */ 371 private fun needsFullscreenBouncer(): Boolean { 372 val mode: KeyguardSecurityModel.SecurityMode = 373 keyguardSecurityModel.getSecurityMode(selectedUserInteractor.getSelectedUserId()) 374 return mode == KeyguardSecurityModel.SecurityMode.SimPin || 375 mode == KeyguardSecurityModel.SecurityMode.SimPuk 376 } 377 378 /** Remove the show runnable from the main handler queue to improve performance. */ 379 private fun cancelShowRunnable() { 380 DejankUtils.removeCallbacks(showRunnable) 381 mainHandler.removeCallbacks(showRunnable) 382 } 383 384 /** Returns whether the primary bouncer is currently showing. */ 385 fun isBouncerShowing(): Boolean { 386 return isShowing.value 387 } 388 389 fun setLastShownPrimarySecurityScreen(securityMode: KeyguardSecurityModel.SecurityMode) { 390 repository.setLastShownSecurityMode(securityMode) 391 } 392 393 /** Whether we want to wait to show the bouncer in case passive auth succeeds. */ 394 private fun usePrimaryBouncerPassiveAuthDelay(): Boolean { 395 val canRunActiveUnlock = 396 currentUserActiveUnlockRunning && 397 keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState() 398 399 return !needsFullscreenBouncer() && 400 (deviceEntryFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock) 401 } 402 403 companion object { 404 private const val TAG = "PrimaryBouncerInteractor" 405 } 406 } 407