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.keyguard.domain.interactor 18 19 import android.content.Context 20 import android.content.res.ColorStateList 21 import android.hardware.biometrics.BiometricSourceType 22 import android.os.Handler 23 import android.os.Trace 24 import android.os.UserHandle 25 import android.os.UserManager 26 import android.util.Log 27 import android.view.View 28 import com.android.keyguard.KeyguardConstants 29 import com.android.keyguard.KeyguardSecurityModel 30 import com.android.keyguard.KeyguardUpdateMonitor 31 import com.android.keyguard.KeyguardUpdateMonitorCallback 32 import com.android.settingslib.Utils 33 import com.android.systemui.DejankUtils 34 import com.android.systemui.R 35 import com.android.systemui.classifier.FalsingCollector 36 import com.android.systemui.dagger.SysUISingleton 37 import com.android.systemui.dagger.qualifiers.Main 38 import com.android.systemui.keyguard.DismissCallbackRegistry 39 import com.android.systemui.keyguard.data.BouncerView 40 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository 41 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants 42 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel 43 import com.android.systemui.plugins.ActivityStarter 44 import com.android.systemui.shared.system.SysUiStatsLog 45 import com.android.systemui.statusbar.phone.KeyguardBypassController 46 import com.android.systemui.statusbar.policy.KeyguardStateController 47 import javax.inject.Inject 48 import kotlinx.coroutines.flow.Flow 49 import kotlinx.coroutines.flow.combine 50 import kotlinx.coroutines.flow.filter 51 import kotlinx.coroutines.flow.filterNotNull 52 import kotlinx.coroutines.flow.map 53 54 /** 55 * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password) 56 * bouncer. 57 */ 58 @SysUISingleton 59 class PrimaryBouncerInteractor 60 @Inject 61 constructor( 62 private val repository: KeyguardBouncerRepository, 63 private val primaryBouncerView: BouncerView, 64 @Main private val mainHandler: Handler, 65 private val keyguardStateController: KeyguardStateController, 66 private val keyguardSecurityModel: KeyguardSecurityModel, 67 private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor, 68 private val falsingCollector: FalsingCollector, 69 private val dismissCallbackRegistry: DismissCallbackRegistry, 70 private val context: Context, 71 private val keyguardUpdateMonitor: KeyguardUpdateMonitor, 72 keyguardBypassController: KeyguardBypassController, 73 ) { 74 /** Whether we want to wait for face auth. */ 75 private val primaryBouncerFaceDelay = 76 keyguardStateController.isFaceAuthEnabled && 77 !keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( 78 KeyguardUpdateMonitor.getCurrentUser() 79 ) && 80 !needsFullscreenBouncer() && 81 keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) && 82 !keyguardBypassController.bypassEnabled 83 84 /** Runnable to show the primary bouncer. */ 85 val showRunnable = Runnable { 86 repository.setPrimaryShow(true) 87 primaryBouncerView.delegate?.showPromptReason(repository.bouncerPromptReason) 88 (repository.bouncerErrorMessage as? String)?.let { 89 repository.setShowMessage( 90 BouncerShowMessageModel(message = it, Utils.getColorError(context)) 91 ) 92 } 93 repository.setPrimaryShowingSoon(false) 94 primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE) 95 } 96 97 val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull() 98 val isShowing: Flow<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 106 val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount 107 /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */ 108 val bouncerExpansion: Flow<Float> = 109 combine(repository.panelExpansionAmount, repository.primaryBouncerShow) { 110 panelExpansion, 111 primaryBouncerIsShowing -> 112 if (primaryBouncerIsShowing) { 113 1f - panelExpansion 114 } else { 115 0f 116 } 117 } 118 /** Allow for interaction when just about fully visible */ 119 val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 } 120 val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing 121 122 /** This callback needs to be a class field so it does not get garbage collected. */ 123 val keyguardUpdateMonitorCallback = 124 object : KeyguardUpdateMonitorCallback() { 125 override fun onBiometricRunningStateChanged( 126 running: Boolean, 127 biometricSourceType: BiometricSourceType? 128 ) { 129 updateSideFpsVisibility() 130 } 131 132 override fun onStrongAuthStateChanged(userId: Int) { 133 updateSideFpsVisibility() 134 } 135 } 136 137 init { 138 keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) 139 } 140 141 // TODO(b/243685699): Move isScrimmed logic to data layer. 142 // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. 143 /** Show the bouncer if necessary and set the relevant states. */ 144 @JvmOverloads 145 fun show(isScrimmed: Boolean) { 146 // Reset some states as we show the bouncer. 147 repository.setKeyguardAuthenticated(null) 148 repository.setPrimaryStartingToHide(false) 149 150 val resumeBouncer = 151 (isBouncerShowing() || repository.primaryBouncerShowingSoon.value) && 152 needsFullscreenBouncer() 153 154 if (!resumeBouncer && isBouncerShowing()) { 155 // If bouncer is visible, the bouncer is already showing. 156 return 157 } 158 159 val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser() 160 if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) { 161 // In split system user mode, we never unlock system user. 162 return 163 } 164 165 Trace.beginSection("KeyguardBouncer#show") 166 repository.setPrimaryScrimmed(isScrimmed) 167 if (isScrimmed) { 168 setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) 169 } 170 171 if (resumeBouncer) { 172 primaryBouncerView.delegate?.resume() 173 // Bouncer is showing the next security screen and we just need to prompt a resume. 174 return 175 } 176 if (primaryBouncerView.delegate?.showNextSecurityScreenOrFinish() == true) { 177 // Keyguard is done. 178 return 179 } 180 181 repository.setPrimaryShowingSoon(true) 182 if (primaryBouncerFaceDelay) { 183 mainHandler.postDelayed(showRunnable, 1200L) 184 } else { 185 DejankUtils.postAfterTraversal(showRunnable) 186 } 187 keyguardStateController.notifyPrimaryBouncerShowing(true) 188 primaryBouncerCallbackInteractor.dispatchStartingToShow() 189 Trace.endSection() 190 } 191 192 /** Sets the correct bouncer states to hide the bouncer. */ 193 fun hide() { 194 Trace.beginSection("KeyguardBouncer#hide") 195 if (isFullyShowing()) { 196 SysUiStatsLog.write( 197 SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, 198 SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN 199 ) 200 dismissCallbackRegistry.notifyDismissCancelled() 201 } 202 203 repository.setPrimaryStartDisappearAnimation(null) 204 falsingCollector.onBouncerHidden() 205 keyguardStateController.notifyPrimaryBouncerShowing(false /* showing */) 206 cancelShowRunnable() 207 repository.setPrimaryShowingSoon(false) 208 repository.setPrimaryShow(false) 209 primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE) 210 Trace.endSection() 211 } 212 213 /** 214 * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f 215 * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the 216 * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show. 217 * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion 218 * of 0f represents the bouncer fully showing. 219 */ 220 fun setPanelExpansion(expansion: Float) { 221 val oldExpansion = repository.panelExpansionAmount.value 222 val expansionChanged = oldExpansion != expansion 223 if (repository.primaryBouncerStartingDisappearAnimation.value == null) { 224 repository.setPanelExpansion(expansion) 225 } 226 227 if ( 228 expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE && 229 oldExpansion != KeyguardBouncerConstants.EXPANSION_VISIBLE 230 ) { 231 falsingCollector.onBouncerShown() 232 primaryBouncerCallbackInteractor.dispatchFullyShown() 233 } else if ( 234 expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN && 235 oldExpansion != KeyguardBouncerConstants.EXPANSION_HIDDEN 236 ) { 237 /* 238 * There are cases where #hide() was not invoked, such as when 239 * NotificationPanelViewController controls the hide animation. Make sure the state gets 240 * updated by calling #hide() directly. 241 */ 242 hide() 243 DejankUtils.postAfterTraversal { primaryBouncerCallbackInteractor.dispatchReset() } 244 primaryBouncerCallbackInteractor.dispatchFullyHidden() 245 } else if ( 246 expansion != KeyguardBouncerConstants.EXPANSION_VISIBLE && 247 oldExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE 248 ) { 249 primaryBouncerCallbackInteractor.dispatchStartingToHide() 250 repository.setPrimaryStartingToHide(true) 251 } 252 if (expansionChanged) { 253 primaryBouncerCallbackInteractor.dispatchExpansionChanged(expansion) 254 } 255 } 256 257 /** Set the initial keyguard message to show when bouncer is shown. */ 258 fun showMessage(message: String?, colorStateList: ColorStateList?) { 259 repository.setShowMessage(BouncerShowMessageModel(message, colorStateList)) 260 } 261 262 /** 263 * Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is 264 * unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we 265 * call cancelAction. 266 */ 267 fun setDismissAction( 268 onDismissAction: ActivityStarter.OnDismissAction?, 269 cancelAction: Runnable? 270 ) { 271 primaryBouncerView.delegate?.setDismissAction(onDismissAction, cancelAction) 272 } 273 274 /** Update the resources of the views. */ 275 fun updateResources() { 276 repository.setResourceUpdateRequests(true) 277 } 278 279 /** Tell the bouncer that keyguard is authenticated. */ 280 fun notifyKeyguardAuthenticated(strongAuth: Boolean) { 281 repository.setKeyguardAuthenticated(strongAuth) 282 } 283 284 /** Update the position of the bouncer when showing. */ 285 fun setKeyguardPosition(position: Float) { 286 repository.setKeyguardPosition(position) 287 } 288 289 /** Notifies that the state change was handled. */ 290 fun notifyKeyguardAuthenticatedHandled() { 291 repository.setKeyguardAuthenticated(null) 292 } 293 294 /** Notifies that the message was shown. */ 295 fun onMessageShown() { 296 repository.setShowMessage(null) 297 } 298 299 /** Notify that the resources have been updated */ 300 fun notifyUpdatedResources() { 301 repository.setResourceUpdateRequests(false) 302 } 303 304 /** Set whether back button is enabled when on the bouncer screen. */ 305 fun setBackButtonEnabled(enabled: Boolean) { 306 repository.setIsBackButtonEnabled(enabled) 307 } 308 309 /** Tell the bouncer to start the pre hide animation. */ 310 fun startDisappearAnimation(runnable: Runnable) { 311 if (willRunDismissFromKeyguard()) { 312 runnable.run() 313 return 314 } 315 316 repository.setPrimaryStartDisappearAnimation(runnable) 317 } 318 319 /** Determine whether to show the side fps animation. */ 320 fun updateSideFpsVisibility() { 321 val sfpsEnabled: Boolean = 322 context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) 323 val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning 324 val isUnlockingWithFpAllowed: Boolean = 325 keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed 326 val toShow = 327 (isBouncerShowing() && 328 sfpsEnabled && 329 fpsDetectionRunning && 330 isUnlockingWithFpAllowed && 331 !isAnimatingAway()) 332 333 if (KeyguardConstants.DEBUG) { 334 Log.d( 335 TAG, 336 ("sideFpsToShow=$toShow\n" + 337 "isBouncerShowing=${isBouncerShowing()}\n" + 338 "configEnabled=$sfpsEnabled\n" + 339 "fpsDetectionRunning=$fpsDetectionRunning\n" + 340 "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" + 341 "isAnimatingAway=${isAnimatingAway()}") 342 ) 343 } 344 repository.setSideFpsShowing(toShow) 345 } 346 347 /** Returns whether bouncer is fully showing. */ 348 fun isFullyShowing(): Boolean { 349 return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) && 350 repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE && 351 repository.primaryBouncerStartingDisappearAnimation.value == null 352 } 353 354 /** Returns whether bouncer is scrimmed. */ 355 fun isScrimmed(): Boolean { 356 return repository.primaryBouncerScrimmed.value 357 } 358 359 /** If bouncer expansion is between 0f and 1f non-inclusive. */ 360 fun isInTransit(): Boolean { 361 return repository.primaryBouncerShowingSoon.value || 362 repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_HIDDEN && 363 repository.panelExpansionAmount.value != KeyguardBouncerConstants.EXPANSION_VISIBLE 364 } 365 366 /** Return whether bouncer is animating away. */ 367 fun isAnimatingAway(): Boolean { 368 return repository.primaryBouncerStartingDisappearAnimation.value != null 369 } 370 371 /** Return whether bouncer will dismiss with actions */ 372 fun willDismissWithAction(): Boolean { 373 return primaryBouncerView.delegate?.willDismissWithActions() == true 374 } 375 376 /** Will the dismissal run from the keyguard layout (instead of from bouncer) */ 377 fun willRunDismissFromKeyguard(): Boolean { 378 return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true 379 } 380 381 /** Returns whether the bouncer should be full screen. */ 382 private fun needsFullscreenBouncer(): Boolean { 383 val mode: KeyguardSecurityModel.SecurityMode = 384 keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()) 385 return mode == KeyguardSecurityModel.SecurityMode.SimPin || 386 mode == KeyguardSecurityModel.SecurityMode.SimPuk 387 } 388 389 /** Remove the show runnable from the main handler queue to improve performance. */ 390 private fun cancelShowRunnable() { 391 DejankUtils.removeCallbacks(showRunnable) 392 mainHandler.removeCallbacks(showRunnable) 393 } 394 395 private fun isBouncerShowing(): Boolean { 396 return repository.primaryBouncerShow.value 397 } 398 399 companion object { 400 private const val TAG = "PrimaryBouncerInteractor" 401 } 402 } 403