• 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.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