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