1 /*
2  * Copyright (C) 2019 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.statusbar.phone
18 
19 import android.annotation.IntDef
20 import android.content.Context
21 import android.content.pm.PackageManager
22 import android.hardware.biometrics.BiometricSourceType
23 import android.provider.Settings
24 import com.android.systemui.Dumpable
25 import com.android.systemui.R
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dump.DumpManager
28 import com.android.systemui.plugins.statusbar.StatusBarStateController
29 import com.android.systemui.shade.ShadeExpansionStateManager
30 import com.android.systemui.statusbar.NotificationLockscreenUserManager
31 import com.android.systemui.statusbar.StatusBarState
32 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
33 import com.android.systemui.statusbar.policy.DevicePostureController
34 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
35 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
36 import com.android.systemui.statusbar.policy.KeyguardStateController
37 import com.android.systemui.tuner.TunerService
38 import java.io.PrintWriter
39 import javax.inject.Inject
40 
41 @SysUISingleton
42 open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassController {
43 
44     private val mKeyguardStateController: KeyguardStateController
45     private val statusBarStateController: StatusBarStateController
46     private val devicePostureController: DevicePostureController
47     @BypassOverride private val bypassOverride: Int
48     private var hasFaceFeature: Boolean
49     @DevicePostureInt private val configFaceAuthSupportedPosture: Int
50     @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN
51     private var pendingUnlock: PendingUnlock? = null
52     private val listeners = mutableListOf<OnBypassStateChangedListener>()
53     private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback {
54         override fun onFaceAuthEnabledChanged() = notifyListeners()
55     }
56 
57     @IntDef(
58         FACE_UNLOCK_BYPASS_NO_OVERRIDE,
59         FACE_UNLOCK_BYPASS_ALWAYS,
60         FACE_UNLOCK_BYPASS_NEVER
61     )
62     @Retention(AnnotationRetention.SOURCE)
63     private annotation class BypassOverride
64 
65     /**
66      * Pending unlock info:
67      *
68      * The pending unlock type which is set if the bypass was blocked when it happened.
69      *
70      * Whether the pending unlock type is strong biometric or non-strong biometric
71      * (i.e. weak or convenience).
72      */
73     private data class PendingUnlock(
74         val pendingUnlockType: BiometricSourceType,
75         val isStrongBiometric: Boolean
76     )
77 
78     lateinit var unlockController: BiometricUnlockController
79     var isPulseExpanding = false
80 
81     /** delegates to [bypassEnabled] but conforms to [StackScrollAlgorithm.BypassController] */
82     override fun isBypassEnabled() = bypassEnabled
83 
84     /**
85      * If face unlock dismisses the lock screen or keeps user on keyguard for the current user.
86      */
87     var bypassEnabled: Boolean = false
88         get() {
89             val enabled = when (bypassOverride) {
90                 FACE_UNLOCK_BYPASS_ALWAYS -> true
91                 FACE_UNLOCK_BYPASS_NEVER -> false
92                 else -> field
93             }
94             return enabled && mKeyguardStateController.isFaceAuthEnabled &&
95                     isPostureAllowedForFaceAuth()
96         }
97         private set(value) {
98             field = value
99             notifyListeners()
100         }
101 
102     var bouncerShowing: Boolean = false
103     var altBouncerShowing: Boolean = false
104     var launchingAffordance: Boolean = false
105     var qsExpanded = false
106 
107     @Inject
108     constructor(
109         context: Context,
110         tunerService: TunerService,
111         statusBarStateController: StatusBarStateController,
112         lockscreenUserManager: NotificationLockscreenUserManager,
113         keyguardStateController: KeyguardStateController,
114         shadeExpansionStateManager: ShadeExpansionStateManager,
115         devicePostureController: DevicePostureController,
116         dumpManager: DumpManager
117     ) {
118         this.mKeyguardStateController = keyguardStateController
119         this.statusBarStateController = statusBarStateController
120         this.devicePostureController = devicePostureController
121 
122         bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
123         configFaceAuthSupportedPosture =
124             context.resources.getInteger(R.integer.config_face_auth_supported_posture)
125 
126         hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
127         if (!hasFaceFeature) {
128             return
129         }
130 
131         if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
132             devicePostureController.addCallback { posture ->
133                 if (postureState != posture) {
134                     postureState = posture
135                     notifyListeners()
136                 }
137             }
138         }
139 
140         dumpManager.registerDumpable("KeyguardBypassController", this)
141         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
142             override fun onStateChanged(newState: Int) {
143                 if (newState != StatusBarState.KEYGUARD) {
144                     pendingUnlock = null
145                 }
146             }
147         })
148 
149         shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
150             val changed = qsExpanded != isQsExpanded
151             qsExpanded = isQsExpanded
152             if (changed && !isQsExpanded) {
153                 maybePerformPendingUnlock()
154             }
155         }
156 
157         val dismissByDefault = if (context.resources.getBoolean(
158                         com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
159         tunerService.addTunable({ key, _ ->
160             bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
161         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
162         lockscreenUserManager.addUserChangedListener(
163                 object : NotificationLockscreenUserManager.UserChangedListener {
164                     override fun onUserChanged(userId: Int) {
165                         pendingUnlock = null
166                     }
167                 })
168     }
169 
170     private fun notifyListeners() = listeners.forEach { it.onBypassStateChanged(bypassEnabled) }
171 
172     /**
173      * Notify that the biometric unlock has happened.
174      *
175      * @return false if we can not wake and unlock right now
176      */
177     fun onBiometricAuthenticated(
178         biometricSourceType: BiometricSourceType,
179         isStrongBiometric: Boolean
180     ): Boolean {
181         if (biometricSourceType == BiometricSourceType.FACE && bypassEnabled) {
182             val can = canBypass()
183             if (!can && (isPulseExpanding || qsExpanded)) {
184                 pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
185             }
186             return can
187         }
188         return true
189     }
190 
191     fun maybePerformPendingUnlock() {
192         if (pendingUnlock != null) {
193             if (onBiometricAuthenticated(pendingUnlock!!.pendingUnlockType,
194                             pendingUnlock!!.isStrongBiometric)) {
195                 unlockController.startWakeAndUnlock(pendingUnlock!!.pendingUnlockType,
196                         pendingUnlock!!.isStrongBiometric)
197                 pendingUnlock = null
198             }
199         }
200     }
201 
202     /**
203      * If keyguard can be dismissed because of bypass.
204      */
205     fun canBypass(): Boolean {
206         if (bypassEnabled) {
207             return when {
208                 bouncerShowing -> true
209                 altBouncerShowing -> true
210                 statusBarStateController.state != StatusBarState.KEYGUARD -> false
211                 launchingAffordance -> false
212                 isPulseExpanding || qsExpanded -> false
213                 else -> true
214             }
215         }
216         return false
217     }
218 
219     fun onStartedGoingToSleep() {
220         pendingUnlock = null
221     }
222 
223     fun isPostureAllowedForFaceAuth(): Boolean {
224         return when (configFaceAuthSupportedPosture) {
225             DEVICE_POSTURE_UNKNOWN -> true
226             else -> (postureState == configFaceAuthSupportedPosture)
227         }
228     }
229 
230     override fun dump(pw: PrintWriter, args: Array<out String>) {
231         pw.println("KeyguardBypassController:")
232         if (pendingUnlock != null) {
233             pw.println("  mPendingUnlock.pendingUnlockType: ${pendingUnlock!!.pendingUnlockType}")
234             pw.println("  mPendingUnlock.isStrongBiometric: ${pendingUnlock!!.isStrongBiometric}")
235         } else {
236             pw.println("  mPendingUnlock: $pendingUnlock")
237         }
238         pw.println("  bypassEnabled: $bypassEnabled")
239         pw.println("  canBypass: ${canBypass()}")
240         pw.println("  bouncerShowing: $bouncerShowing")
241         pw.println("  altBouncerShowing: $altBouncerShowing")
242         pw.println("  isPulseExpanding: $isPulseExpanding")
243         pw.println("  launchingAffordance: $launchingAffordance")
244         pw.println("  qSExpanded: $qsExpanded")
245         pw.println("  hasFaceFeature: $hasFaceFeature")
246         pw.println("  postureState: $postureState")
247     }
248 
249     /** Registers a listener for bypass state changes. */
250     fun registerOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
251         val start = listeners.isEmpty()
252         listeners.add(listener)
253         if (start) {
254             mKeyguardStateController.addCallback(faceAuthEnabledChangedCallback)
255         }
256     }
257 
258     /**
259      * Unregisters a listener for bypass state changes, previous registered with
260      * [registerOnBypassStateChangedListener]
261      */
262     fun unregisterOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
263         listeners.remove(listener)
264         if (listeners.isEmpty()) {
265             mKeyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
266         }
267     }
268 
269     /** Listener for bypass state change events.  */
270     interface OnBypassStateChangedListener {
271         /** Invoked when bypass becomes enabled or disabled. */
272         fun onBypassStateChanged(isEnabled: Boolean)
273     }
274 
275     companion object {
276         private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
277         private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
278         private const val FACE_UNLOCK_BYPASS_NEVER = 2
279     }
280 }
281