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