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