• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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