• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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.annotation.SuppressLint
20 import android.app.AlarmManager
21 import android.app.PendingIntent
22 import android.content.BroadcastReceiver
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.provider.Settings
27 import android.provider.Settings.Secure
28 import com.android.app.tracing.coroutines.launchTraced as launch
29 import com.android.internal.widget.LockPatternUtils
30 import com.android.systemui.dagger.SysUISingleton
31 import com.android.systemui.dagger.qualifiers.Application
32 import com.android.systemui.keyguard.KeyguardViewMediator
33 import com.android.systemui.keyguard.KeyguardWmStateRefactor
34 import com.android.systemui.keyguard.data.repository.KeyguardRepository
35 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
36 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
37 import com.android.systemui.keyguard.shared.model.KeyguardState
38 import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAsleepInState
39 import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.deviceIsAwakeInState
40 import com.android.systemui.keyguard.shared.model.TransitionStep
41 import com.android.systemui.power.domain.interactor.PowerInteractor
42 import com.android.systemui.power.shared.model.WakeSleepReason
43 import com.android.systemui.scene.shared.model.Scenes
44 import com.android.systemui.shade.ShadeDisplayAware
45 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
46 import com.android.systemui.util.kotlin.sample
47 import com.android.systemui.util.settings.SecureSettings
48 import com.android.systemui.util.settings.SystemSettings
49 import com.android.systemui.util.time.SystemClock
50 import javax.inject.Inject
51 import kotlin.math.max
52 import kotlin.math.min
53 import kotlinx.coroutines.CoroutineScope
54 import kotlinx.coroutines.flow.combine
55 import kotlinx.coroutines.flow.distinctUntilChanged
56 import kotlinx.coroutines.flow.distinctUntilChangedBy
57 import kotlinx.coroutines.flow.filter
58 import kotlinx.coroutines.flow.map
59 import kotlinx.coroutines.flow.merge
60 import kotlinx.coroutines.flow.onStart
61 
62 /**
63  * Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
64  * through LOCKSCREEN or a BOUNCER state.
65  *
66  * This is possible in the following scenarios:
67  * - The keyguard is not enabled, either from an app request (SUW does this), or by the security
68  *   "None" setting.
69  * - The keyguard was suppressed via adb.
70  * - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
71  *   specifically is referred to throughout the codebase as "wake and unlock".
72  * - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
73  * - The power button was pressed, but "power button instantly locks" is disabled and the "lock
74  *   after screen timeout" duration has not elapsed.
75  *
76  * In these cases, no (further) authentication is required, and we can transition directly from
77  * AOD/DOZING -> GONE.
78  */
79 @SysUISingleton
80 class KeyguardWakeDirectlyToGoneInteractor
81 @Inject
82 constructor(
83     @Application private val scope: CoroutineScope,
84     @ShadeDisplayAware private val context: Context,
85     private val repository: KeyguardRepository,
86     private val systemClock: SystemClock,
87     private val alarmManager: AlarmManager,
88     private val transitionInteractor: KeyguardTransitionInteractor,
89     private val powerInteractor: PowerInteractor,
90     private val secureSettings: SecureSettings,
91     private val lockPatternUtils: LockPatternUtils,
92     private val systemSettings: SystemSettings,
93     private val selectedUserInteractor: SelectedUserInteractor,
94     keyguardEnabledInteractor: KeyguardEnabledInteractor,
95     keyguardServiceShowLockscreenInteractor: KeyguardServiceShowLockscreenInteractor,
96     keyguardInteractor: KeyguardInteractor,
97 ) {
98 
99     /**
100      * Whether the keyguard was suppressed as of the most recent wakefulness event or lockNow
101      * command. Keyguard suppression can only be queried (there is no callback available), and
102      * legacy code only queried the value in onStartedGoingToSleep and doKeyguardTimeout. Tests now
103      * depend on that behavior, so for now, we'll replicate it here.
104      */
105     private val shouldSuppressKeyguard =
106         merge(
107                 powerInteractor.isAwake,
108                 // Update only when doKeyguardTimeout is called, not on fold or other events, to
109                 // match
110                 // pre-existing logic.
111                 keyguardServiceShowLockscreenInteractor.showNowEvents.filter {
112                     it == ShowWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON
113                 },
114             )
115             .map { keyguardEnabledInteractor.isKeyguardSuppressed() }
116             // Default to false, so that flows that combine this one emit prior to the first
117             // wakefulness emission.
118             .onStart { emit(false) }
119 
120     /**
121      * Whether we can wake from AOD/DOZING or DREAMING directly to GONE, bypassing
122      * LOCKSCREEN/BOUNCER states.
123      *
124      * This is possible in the following cases:
125      * - Keyguard is disabled, either from an app request or from security being set to "None".
126      * - Keyguard is suppressed, via adb locksettings.
127      * - We're wake and unlocking (fingerprint auth occurred while asleep).
128      * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
129      * - We're DREAMING and dismissible.
130      * - We're already GONE and not transitioning out of GONE. Technically you're already awake when
131      *   GONE, but this makes it easier to reason about this state (for example, if
132      *   canWakeDirectlyToGone, don't tell WM to pause the top activity - something you should never
133      *   do while GONE as well).
134      */
135     val canWakeDirectlyToGone =
136         combine(
137                 repository.isKeyguardEnabled,
138                 shouldSuppressKeyguard,
139                 repository.biometricUnlockState,
140                 repository.canIgnoreAuthAndReturnToGone,
141                 transitionInteractor.currentKeyguardState,
142                 transitionInteractor.startedKeyguardTransitionStep,
143             ) { values ->
144                 val keyguardEnabled = values[0] as Boolean
145                 val shouldSuppressKeyguard = values[1] as Boolean
146                 val biometricUnlockState = values[2] as BiometricUnlockModel
147                 val canIgnoreAuthAndReturnToGone = values[3] as Boolean
148                 val currentState = values[4] as KeyguardState
149                 val startedStep = values[5] as TransitionStep
150                 (!keyguardEnabled || shouldSuppressKeyguard) ||
151                     BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
152                     canIgnoreAuthAndReturnToGone ||
153                     (currentState == KeyguardState.DREAMING &&
154                         keyguardInteractor.isKeyguardDismissible.value) ||
155                     (currentState == KeyguardState.GONE && startedStep.to == KeyguardState.GONE)
156             }
157             .distinctUntilChanged()
158 
159     /**
160      * Counter that is incremented every time we wake up or stop dreaming. Upon sleeping/dreaming,
161      * we put the current value of this counter into the intent extras of the timeout alarm intent.
162      * If this value has changed by the time we receive the intent, it is discarded since it's out
163      * of date.
164      */
165     var timeoutCounter = 0
166 
167     var isAwake = false
168 
169     private val broadcastReceiver: BroadcastReceiver =
170         object : BroadcastReceiver() {
171             override fun onReceive(context: Context, intent: Intent) {
172                 if (DELAYED_KEYGUARD_ACTION == intent.action) {
173                     val sequence = intent.getIntExtra(SEQ_EXTRA_KEY, 0)
174                     synchronized(this) {
175                         if (timeoutCounter == sequence) {
176                             // If the sequence # matches, we have not woken up or stopped dreaming
177                             // since
178                             // the alarm was set. That means this is still relevant - the lock
179                             // timeout
180                             // has elapsed, so let the repository know that we can no longer return
181                             // to
182                             // GONE without authenticating.
183                             repository.setCanIgnoreAuthAndReturnToGone(false)
184                         }
185                     }
186                 }
187             }
188         }
189 
190     init {
191         setOrCancelAlarmFromWakefulness()
192         listenForWakeToClearCanIgnoreAuth()
193         registerBroadcastReceiver()
194     }
195 
196     fun onDreamingStarted() {
197         // If we start dreaming while awake, lock after the normal timeout.
198         if (isAwake) {
199             setResetCanIgnoreAuthAlarm()
200         }
201     }
202 
203     fun onDreamingStopped() {
204         // Cancel the timeout if we stop dreaming while awake.
205         if (isAwake) {
206             cancelCanIgnoreAuthAlarm()
207         }
208     }
209 
210     private fun setOrCancelAlarmFromWakefulness() {
211         scope.launch {
212             powerInteractor.detailedWakefulness
213                 .distinctUntilChangedBy { it.isAwake() }
214                 .sample(
215                     transitionInteractor.isCurrentlyIn(
216                         Scenes.Gone,
217                         stateWithoutSceneContainer = KeyguardState.GONE,
218                     ),
219                     ::Pair,
220                 )
221                 .collect { (wakefulness, finishedInGone) ->
222                     // Save isAwake for use in onDreamingStarted/onDreamingStopped.
223                     this@KeyguardWakeDirectlyToGoneInteractor.isAwake = wakefulness.isAwake()
224 
225                     // If we're sleeping from GONE, check the timeout and lock instantly settings.
226                     // These are not relevant if we're coming from non-GONE states.
227                     if (!isAwake && finishedInGone) {
228                         val lockTimeoutDuration = getCanIgnoreAuthAndReturnToGoneDuration()
229 
230                         // If the screen timed out and went to sleep, and the lock timeout is > 0ms,
231                         // then we can return to GONE until that duration elapses. If the power
232                         // button was pressed but "instantly locks" is disabled, then we can also
233                         // return to GONE until the timeout duration elapses.
234                         if (
235                             (wakefulness.lastSleepReason == WakeSleepReason.TIMEOUT &&
236                                 lockTimeoutDuration > 0) ||
237                                 (wakefulness.lastSleepReason == WakeSleepReason.POWER_BUTTON &&
238                                     !willLockImmediately())
239                         ) {
240 
241                             // Let the repository know that we can return to GONE until we notify
242                             // it otherwise.
243                             repository.setCanIgnoreAuthAndReturnToGone(true)
244                             setResetCanIgnoreAuthAlarm()
245                         }
246                     } else if (isAwake) {
247                         // If we're waking up, ignore the alarm if it goes off since it's no longer
248                         // relevant. Once a wake KeyguardTransition is started, we'll also clear the
249                         // canIgnoreAuthAndReturnToGone value in listenForWakeToClearCanIgnoreAuth.
250                         cancelCanIgnoreAuthAlarm()
251                     }
252                 }
253         }
254     }
255 
256     /** Clears the canIgnoreAuthAndReturnToGone value upon waking. */
257     private fun listenForWakeToClearCanIgnoreAuth() {
258         scope.launch {
259             transitionInteractor
260                 .isInTransitionWhere(
261                     fromStatePredicate = { deviceIsAsleepInState(it) },
262                     toStatePredicate = { deviceIsAwakeInState(it) },
263                 )
264                 .collect {
265                     // This value is reset when the timeout alarm fires, but if the device is woken
266                     // back up before then, it needs to be reset here. The alarm is cancelled
267                     // immediately upon waking up, but since this value is used by keyguard
268                     // transition internals to decide whether we can transition to GONE, wait until
269                     // that decision is made before resetting it.
270                     repository.setCanIgnoreAuthAndReturnToGone(false)
271                 }
272         }
273     }
274 
275     /**
276      * Registers the broadcast receiver to receive the alarm intent.
277      *
278      * TODO(b/351817381): Investigate using BroadcastDispatcher vs. ignoring this lint warning.
279      */
280     @SuppressLint("WrongConstant", "RegisterReceiverViaContext")
281     private fun registerBroadcastReceiver() {
282         val delayedActionFilter = IntentFilter()
283         delayedActionFilter.addAction(KeyguardViewMediator.DELAYED_KEYGUARD_ACTION)
284         // TODO(b/346803756): Listen for DELAYED_LOCK_PROFILE_ACTION.
285         delayedActionFilter.priority = IntentFilter.SYSTEM_HIGH_PRIORITY
286         context.registerReceiver(
287             broadcastReceiver,
288             delayedActionFilter,
289             SYSTEMUI_PERMISSION,
290             null /* scheduler */,
291             Context.RECEIVER_EXPORTED_UNAUDITED,
292         )
293     }
294 
295     /** Set an alarm for */
296     private fun setResetCanIgnoreAuthAlarm() {
297         if (!KeyguardWmStateRefactor.isEnabled) {
298             return
299         }
300 
301         val intent =
302             Intent(DELAYED_KEYGUARD_ACTION).apply {
303                 setPackage(context.packageName)
304                 putExtra(SEQ_EXTRA_KEY, timeoutCounter)
305                 addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
306             }
307 
308         val sender =
309             PendingIntent.getBroadcast(
310                 context,
311                 0,
312                 intent,
313                 PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
314             )
315 
316         val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
317         alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, sender)
318 
319         // TODO(b/346803756): Migrate support for child profiles.
320     }
321 
322     /**
323      * Cancel the timeout by incrementing the counter so that we ignore the intent when it's
324      * received.
325      */
326     private fun cancelCanIgnoreAuthAlarm() {
327         timeoutCounter++
328     }
329 
330     /**
331      * Whether pressing the power button locks the device immediately; vs. waiting for a specified
332      * timeout first.
333      */
334     private fun willLockImmediately(
335         userId: Int = selectedUserInteractor.getSelectedUserId()
336     ): Boolean {
337         return lockPatternUtils.getPowerButtonInstantlyLocks(userId) ||
338             !lockPatternUtils.isSecure(userId)
339     }
340 
341     /**
342      * Returns the duration within which we can return to GONE without auth after a screen timeout
343      * (or power button press, if lock instantly is disabled).
344      *
345      * This takes into account the user's settings as well as device policy maximums.
346      */
347     private fun getCanIgnoreAuthAndReturnToGoneDuration(
348         userId: Int = selectedUserInteractor.getSelectedUserId()
349     ): Long {
350         // The timeout duration from settings (Security > Device Unlock > Gear icon > "Lock after
351         // screen timeout".
352         val durationSetting: Long =
353             secureSettings
354                 .getIntForUser(
355                     Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
356                     KEYGUARD_CAN_IGNORE_AUTH_DURATION,
357                     userId,
358                 )
359                 .toLong()
360 
361         // Device policy maximum timeout.
362         val durationDevicePolicyMax =
363             lockPatternUtils.devicePolicyManager.getMaximumTimeToLock(null, userId)
364 
365         return if (durationDevicePolicyMax <= 0) {
366             durationSetting
367         } else {
368             var displayTimeout =
369                 systemSettings
370                     .getIntForUser(
371                         Settings.System.SCREEN_OFF_TIMEOUT,
372                         KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
373                         userId,
374                     )
375                     .toLong()
376 
377             // Ignore negative values. I don't know why this would be negative, but this check has
378             // been around since 2016 and I see no upside to removing it.
379             displayTimeout = max(displayTimeout, 0)
380 
381             // Respect the shorter of: the device policy (maximum duration between last user action
382             // and fully locking) or the "Lock after screen timeout" setting.
383             max(min(durationDevicePolicyMax - displayTimeout, durationSetting), 0)
384         }
385     }
386 
387     companion object {
388         private const val DELAYED_KEYGUARD_ACTION =
389             "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"
390         private const val DELAYED_LOCK_PROFILE_ACTION =
391             "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK"
392         private const val SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"
393         private const val SEQ_EXTRA_KEY = "count"
394 
395         private const val KEYGUARD_CAN_IGNORE_AUTH_DURATION = 5000
396         private const val KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000
397     }
398 }
399