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