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 static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; 20 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; 21 22 import android.annotation.NonNull; 23 import android.os.Bundle; 24 import android.os.PowerManager; 25 import android.os.SystemClock; 26 import android.os.SystemProperties; 27 import android.util.Log; 28 import android.view.MotionEvent; 29 import android.view.View; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.keyguard.KeyguardUpdateMonitor; 33 import com.android.systemui.assist.AssistManager; 34 import com.android.systemui.biometrics.AuthController; 35 import com.android.systemui.dagger.SysUISingleton; 36 import com.android.systemui.doze.DozeHost; 37 import com.android.systemui.doze.DozeLog; 38 import com.android.systemui.doze.DozeReceiver; 39 import com.android.systemui.keyguard.KeyguardViewMediator; 40 import com.android.systemui.keyguard.WakefulnessLifecycle; 41 import com.android.systemui.statusbar.NotificationShadeWindowController; 42 import com.android.systemui.statusbar.PulseExpansionHandler; 43 import com.android.systemui.statusbar.StatusBarState; 44 import com.android.systemui.statusbar.SysuiStatusBarStateController; 45 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; 46 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 47 import com.android.systemui.statusbar.policy.BatteryController; 48 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 49 50 import java.util.ArrayList; 51 52 import javax.inject.Inject; 53 54 import dagger.Lazy; 55 56 /** 57 * Implementation of DozeHost for SystemUI. 58 */ 59 @SysUISingleton 60 public final class DozeServiceHost implements DozeHost { 61 private static final String TAG = "DozeServiceHost"; 62 private final ArrayList<Callback> mCallbacks = new ArrayList<>(); 63 private final DozeLog mDozeLog; 64 private final PowerManager mPowerManager; 65 private boolean mAnimateWakeup; 66 private boolean mAnimateScreenOff; 67 private boolean mIgnoreTouchWhilePulsing; 68 private Runnable mPendingScreenOffCallback; 69 @VisibleForTesting 70 boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean( 71 "persist.sysui.wake_performs_auth", true); 72 private boolean mDozingRequested; 73 private boolean mPulsing; 74 private final WakefulnessLifecycle mWakefulnessLifecycle; 75 private final SysuiStatusBarStateController mStatusBarStateController; 76 private final DeviceProvisionedController mDeviceProvisionedController; 77 private final HeadsUpManagerPhone mHeadsUpManagerPhone; 78 private final BatteryController mBatteryController; 79 private final ScrimController mScrimController; 80 private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; 81 private final KeyguardViewMediator mKeyguardViewMediator; 82 private final Lazy<AssistManager> mAssistManagerLazy; 83 private final DozeScrimController mDozeScrimController; 84 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 85 private final PulseExpansionHandler mPulseExpansionHandler; 86 private final NotificationShadeWindowController mNotificationShadeWindowController; 87 private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator; 88 private NotificationShadeWindowViewController mNotificationShadeWindowViewController; 89 private final AuthController mAuthController; 90 private final NotificationIconAreaController mNotificationIconAreaController; 91 private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 92 private NotificationPanelViewController mNotificationPanel; 93 private View mAmbientIndicationContainer; 94 private StatusBar mStatusBar; 95 private boolean mSuppressed; 96 97 @Inject DozeServiceHost(DozeLog dozeLog, PowerManager powerManager, WakefulnessLifecycle wakefulnessLifecycle, SysuiStatusBarStateController statusBarStateController, DeviceProvisionedController deviceProvisionedController, HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController, ScrimController scrimController, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, KeyguardViewMediator keyguardViewMediator, Lazy<AssistManager> assistManagerLazy, DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor, PulseExpansionHandler pulseExpansionHandler, NotificationShadeWindowController notificationShadeWindowController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, AuthController authController, NotificationIconAreaController notificationIconAreaController)98 public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager, 99 WakefulnessLifecycle wakefulnessLifecycle, 100 SysuiStatusBarStateController statusBarStateController, 101 DeviceProvisionedController deviceProvisionedController, 102 HeadsUpManagerPhone headsUpManagerPhone, BatteryController batteryController, 103 ScrimController scrimController, 104 Lazy<BiometricUnlockController> biometricUnlockControllerLazy, 105 KeyguardViewMediator keyguardViewMediator, 106 Lazy<AssistManager> assistManagerLazy, 107 DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor, 108 PulseExpansionHandler pulseExpansionHandler, 109 NotificationShadeWindowController notificationShadeWindowController, 110 NotificationWakeUpCoordinator notificationWakeUpCoordinator, 111 AuthController authController, 112 NotificationIconAreaController notificationIconAreaController) { 113 super(); 114 mDozeLog = dozeLog; 115 mPowerManager = powerManager; 116 mWakefulnessLifecycle = wakefulnessLifecycle; 117 mStatusBarStateController = statusBarStateController; 118 mDeviceProvisionedController = deviceProvisionedController; 119 mHeadsUpManagerPhone = headsUpManagerPhone; 120 mBatteryController = batteryController; 121 mScrimController = scrimController; 122 mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; 123 mKeyguardViewMediator = keyguardViewMediator; 124 mAssistManagerLazy = assistManagerLazy; 125 mDozeScrimController = dozeScrimController; 126 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 127 mPulseExpansionHandler = pulseExpansionHandler; 128 mNotificationShadeWindowController = notificationShadeWindowController; 129 mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; 130 mAuthController = authController; 131 mNotificationIconAreaController = notificationIconAreaController; 132 } 133 134 // TODO: we should try to not pass status bar in here if we can avoid it. 135 136 /** 137 * Initialize instance with objects only available later during execution. 138 */ initialize( StatusBar statusBar, StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationShadeWindowViewController notificationShadeWindowViewController, NotificationPanelViewController notificationPanel, View ambientIndicationContainer)139 public void initialize( 140 StatusBar statusBar, 141 StatusBarKeyguardViewManager statusBarKeyguardViewManager, 142 NotificationShadeWindowViewController notificationShadeWindowViewController, 143 NotificationPanelViewController notificationPanel, 144 View ambientIndicationContainer) { 145 mStatusBar = statusBar; 146 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 147 mNotificationPanel = notificationPanel; 148 mNotificationShadeWindowViewController = notificationShadeWindowViewController; 149 mAmbientIndicationContainer = ambientIndicationContainer; 150 } 151 152 153 @Override toString()154 public String toString() { 155 return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]"; 156 } 157 firePowerSaveChanged(boolean active)158 void firePowerSaveChanged(boolean active) { 159 for (Callback callback : mCallbacks) { 160 callback.onPowerSaveChanged(active); 161 } 162 } 163 fireNotificationPulse(NotificationEntry entry)164 void fireNotificationPulse(NotificationEntry entry) { 165 Runnable pulseSuppressedListener = () -> { 166 entry.setPulseSuppressed(true); 167 mNotificationIconAreaController.updateAodNotificationIcons(); 168 }; 169 for (Callback callback : mCallbacks) { 170 callback.onNotificationAlerted(pulseSuppressedListener); 171 } 172 } 173 getDozingRequested()174 boolean getDozingRequested() { 175 return mDozingRequested; 176 } 177 isPulsing()178 boolean isPulsing() { 179 return mPulsing; 180 } 181 182 183 @Override addCallback(@onNull Callback callback)184 public void addCallback(@NonNull Callback callback) { 185 mCallbacks.add(callback); 186 } 187 188 @Override removeCallback(@onNull Callback callback)189 public void removeCallback(@NonNull Callback callback) { 190 mCallbacks.remove(callback); 191 } 192 193 @Override startDozing()194 public void startDozing() { 195 if (!mDozingRequested) { 196 mDozingRequested = true; 197 updateDozing(); 198 mDozeLog.traceDozing(mStatusBarStateController.isDozing()); 199 mStatusBar.updateIsKeyguard(); 200 } 201 } 202 updateDozing()203 void updateDozing() { 204 // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked. 205 boolean 206 dozing = 207 mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD 208 || mBiometricUnlockControllerLazy.get().getMode() 209 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; 210 // When in wake-and-unlock we may not have received a change to StatusBarState 211 // but we still should not be dozing, manually set to false. 212 if (mBiometricUnlockControllerLazy.get().getMode() 213 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK) { 214 dozing = false; 215 } 216 217 mStatusBarStateController.setIsDozing(dozing); 218 } 219 220 @Override pulseWhileDozing(@onNull PulseCallback callback, int reason)221 public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) { 222 if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) { 223 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, 224 "com.android.systemui:LONG_PRESS"); 225 mAssistManagerLazy.get().startAssist(new Bundle()); 226 return; 227 } 228 229 if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { 230 mScrimController.setWakeLockScreenSensorActive(true); 231 } 232 233 boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN 234 && mWakeLockScreenPerformsAuth; 235 // Set the state to pulsing, so ScrimController will know what to do once we ask it to 236 // execute the transition. The pulse callback will then be invoked when the scrims 237 // are black, indicating that StatusBar is ready to present the rest of the UI. 238 mPulsing = true; 239 mDozeScrimController.pulse(new PulseCallback() { 240 @Override 241 public void onPulseStarted() { 242 callback.onPulseStarted(); 243 mStatusBar.updateNotificationPanelTouchState(); 244 setPulsing(true); 245 } 246 247 @Override 248 public void onPulseFinished() { 249 mPulsing = false; 250 callback.onPulseFinished(); 251 mStatusBar.updateNotificationPanelTouchState(); 252 mScrimController.setWakeLockScreenSensorActive(false); 253 setPulsing(false); 254 } 255 256 private void setPulsing(boolean pulsing) { 257 mStatusBarKeyguardViewManager.setPulsing(pulsing); 258 mKeyguardViewMediator.setPulsing(pulsing); 259 mNotificationPanel.setPulsing(pulsing); 260 mStatusBarStateController.setPulsing(pulsing); 261 mIgnoreTouchWhilePulsing = false; 262 if (mKeyguardUpdateMonitor != null && passiveAuthInterrupt) { 263 mKeyguardUpdateMonitor.onAuthInterruptDetected(pulsing /* active */); 264 } 265 mStatusBar.updateScrimController(); 266 mPulseExpansionHandler.setPulsing(pulsing); 267 mNotificationWakeUpCoordinator.setPulsing(pulsing); 268 } 269 }, reason); 270 // DozeScrimController is in pulse state, now let's ask ScrimController to start 271 // pulsing and draw the black frame, if necessary. 272 mStatusBar.updateScrimController(); 273 } 274 275 @Override stopDozing()276 public void stopDozing() { 277 if (mDozingRequested) { 278 mDozingRequested = false; 279 updateDozing(); 280 mDozeLog.traceDozing(mStatusBarStateController.isDozing()); 281 } 282 } 283 284 @Override onIgnoreTouchWhilePulsing(boolean ignore)285 public void onIgnoreTouchWhilePulsing(boolean ignore) { 286 if (ignore != mIgnoreTouchWhilePulsing) { 287 mDozeLog.tracePulseTouchDisabledByProx(ignore); 288 } 289 mIgnoreTouchWhilePulsing = ignore; 290 if (mStatusBarStateController.isDozing() && ignore) { 291 mNotificationShadeWindowViewController.cancelCurrentTouch(); 292 } 293 } 294 295 @Override dozeTimeTick()296 public void dozeTimeTick() { 297 mNotificationPanel.dozeTimeTick(); 298 mAuthController.dozeTimeTick(); 299 if (mAmbientIndicationContainer instanceof DozeReceiver) { 300 ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick(); 301 } 302 } 303 304 @Override isPowerSaveActive()305 public boolean isPowerSaveActive() { 306 return mBatteryController.isAodPowerSave(); 307 } 308 309 @Override isPulsingBlocked()310 public boolean isPulsingBlocked() { 311 return mBiometricUnlockControllerLazy.get().getMode() 312 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; 313 } 314 315 @Override isProvisioned()316 public boolean isProvisioned() { 317 return mDeviceProvisionedController.isDeviceProvisioned() 318 && mDeviceProvisionedController.isCurrentUserSetup(); 319 } 320 321 @Override isBlockingDoze()322 public boolean isBlockingDoze() { 323 if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) { 324 Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated"); 325 return true; 326 } 327 return false; 328 } 329 330 @Override extendPulse(int reason)331 public void extendPulse(int reason) { 332 if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) { 333 mScrimController.setWakeLockScreenSensorActive(true); 334 } 335 if (mDozeScrimController.isPulsing() && mHeadsUpManagerPhone.hasNotifications()) { 336 mHeadsUpManagerPhone.extendHeadsUp(); 337 } else { 338 mDozeScrimController.extendPulse(); 339 } 340 } 341 342 @Override stopPulsing()343 public void stopPulsing() { 344 if (mDozeScrimController.isPulsing()) { 345 mDozeScrimController.pulseOutNow(); 346 } 347 } 348 349 @Override setAnimateWakeup(boolean animateWakeup)350 public void setAnimateWakeup(boolean animateWakeup) { 351 if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE 352 || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING) { 353 // Too late to change the wakeup animation. 354 return; 355 } 356 mAnimateWakeup = animateWakeup; 357 } 358 359 @Override setAnimateScreenOff(boolean animateScreenOff)360 public void setAnimateScreenOff(boolean animateScreenOff) { 361 mAnimateScreenOff = animateScreenOff; 362 } 363 364 @Override onSlpiTap(float screenX, float screenY)365 public void onSlpiTap(float screenX, float screenY) { 366 if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null 367 && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { 368 int[] locationOnScreen = new int[2]; 369 mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen); 370 float viewX = screenX - locationOnScreen[0]; 371 float viewY = screenY - locationOnScreen[1]; 372 if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth() 373 && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) { 374 375 // Dispatch a tap 376 long now = SystemClock.elapsedRealtime(); 377 MotionEvent ev = MotionEvent.obtain( 378 now, now, MotionEvent.ACTION_DOWN, screenX, screenY, 0); 379 mAmbientIndicationContainer.dispatchTouchEvent(ev); 380 ev.recycle(); 381 ev = MotionEvent.obtain( 382 now, now, MotionEvent.ACTION_UP, screenX, screenY, 0); 383 mAmbientIndicationContainer.dispatchTouchEvent(ev); 384 ev.recycle(); 385 } 386 } 387 } 388 389 @Override setDozeScreenBrightness(int brightness)390 public void setDozeScreenBrightness(int brightness) { 391 mDozeLog.traceDozeScreenBrightness(brightness); 392 mNotificationShadeWindowController.setDozeScreenBrightness(brightness); 393 } 394 395 @Override setAodDimmingScrim(float scrimOpacity)396 public void setAodDimmingScrim(float scrimOpacity) { 397 mDozeLog.traceSetAodDimmingScrim(scrimOpacity); 398 mScrimController.setAodFrontScrimAlpha(scrimOpacity); 399 } 400 401 @Override prepareForGentleSleep(Runnable onDisplayOffCallback)402 public void prepareForGentleSleep(Runnable onDisplayOffCallback) { 403 if (mPendingScreenOffCallback != null) { 404 Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one."); 405 } 406 mPendingScreenOffCallback = onDisplayOffCallback; 407 mStatusBar.updateScrimController(); 408 } 409 410 @Override cancelGentleSleep()411 public void cancelGentleSleep() { 412 mPendingScreenOffCallback = null; 413 if (mScrimController.getState() == ScrimState.OFF) { 414 mStatusBar.updateScrimController(); 415 } 416 } 417 418 /** 419 * When the dozing host is waiting for scrims to fade out to change the display state. 420 */ hasPendingScreenOffCallback()421 boolean hasPendingScreenOffCallback() { 422 return mPendingScreenOffCallback != null; 423 } 424 425 /** 426 * Executes an nullifies the pending display state callback. 427 * 428 * @see #hasPendingScreenOffCallback() 429 * @see #prepareForGentleSleep(Runnable) 430 */ executePendingScreenOffCallback()431 void executePendingScreenOffCallback() { 432 if (mPendingScreenOffCallback == null) { 433 return; 434 } 435 mPendingScreenOffCallback.run(); 436 mPendingScreenOffCallback = null; 437 } 438 shouldAnimateWakeup()439 boolean shouldAnimateWakeup() { 440 return mAnimateWakeup; 441 } 442 shouldAnimateScreenOff()443 boolean shouldAnimateScreenOff() { 444 return mAnimateScreenOff; 445 } 446 getIgnoreTouchWhilePulsing()447 boolean getIgnoreTouchWhilePulsing() { 448 return mIgnoreTouchWhilePulsing; 449 } 450 setDozeSuppressed(boolean suppressed)451 void setDozeSuppressed(boolean suppressed) { 452 if (suppressed == mSuppressed) { 453 return; 454 } 455 mSuppressed = suppressed; 456 mDozeLog.traceDozingSuppressed(mSuppressed); 457 for (Callback callback : mCallbacks) { 458 callback.onDozeSuppressedChanged(suppressed); 459 } 460 } 461 isDozeSuppressed()462 public boolean isDozeSuppressed() { 463 return mSuppressed; 464 } 465 } 466