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