1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.statusbar.phone; 16 17 import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED; 18 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; 19 import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG; 20 import static com.android.systemui.statusbar.phone.StatusBar.SPEW; 21 22 import android.annotation.Nullable; 23 import android.app.KeyguardManager; 24 import android.content.Context; 25 import android.os.RemoteException; 26 import android.os.ServiceManager; 27 import android.os.SystemClock; 28 import android.service.notification.NotificationListenerService; 29 import android.service.notification.StatusBarNotification; 30 import android.service.vr.IVrManager; 31 import android.service.vr.IVrStateCallbacks; 32 import android.util.Log; 33 import android.util.Slog; 34 import android.view.View; 35 import android.view.accessibility.AccessibilityManager; 36 import android.widget.TextView; 37 38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 39 import com.android.internal.statusbar.IStatusBarService; 40 import com.android.internal.statusbar.NotificationVisibility; 41 import com.android.internal.widget.MessagingGroup; 42 import com.android.internal.widget.MessagingMessage; 43 import com.android.keyguard.KeyguardUpdateMonitor; 44 import com.android.systemui.Dependency; 45 import com.android.systemui.ForegroundServiceNotificationListener; 46 import com.android.systemui.InitController; 47 import com.android.systemui.R; 48 import com.android.systemui.plugins.ActivityStarter; 49 import com.android.systemui.plugins.ActivityStarter.OnDismissAction; 50 import com.android.systemui.plugins.statusbar.StatusBarStateController; 51 import com.android.systemui.statusbar.CommandQueue; 52 import com.android.systemui.statusbar.KeyguardIndicationController; 53 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 54 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 55 import com.android.systemui.statusbar.NotificationMediaManager; 56 import com.android.systemui.statusbar.NotificationPresenter; 57 import com.android.systemui.statusbar.NotificationRemoteInputManager; 58 import com.android.systemui.statusbar.NotificationShadeWindowController; 59 import com.android.systemui.statusbar.NotificationViewHierarchyManager; 60 import com.android.systemui.statusbar.StatusBarState; 61 import com.android.systemui.statusbar.SysuiStatusBarStateController; 62 import com.android.systemui.statusbar.notification.AboveShelfObserver; 63 import com.android.systemui.statusbar.notification.DynamicPrivacyController; 64 import com.android.systemui.statusbar.notification.NotificationEntryListener; 65 import com.android.systemui.statusbar.notification.NotificationEntryManager; 66 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 67 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; 68 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; 69 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; 70 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; 71 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; 72 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 73 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 74 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; 75 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; 76 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; 77 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; 78 import com.android.systemui.statusbar.policy.ConfigurationController; 79 import com.android.systemui.statusbar.policy.KeyguardStateController; 80 81 import java.util.List; 82 83 public class StatusBarNotificationPresenter implements NotificationPresenter, 84 ConfigurationController.ConfigurationListener, 85 NotificationRowBinderImpl.BindRowCallback, 86 CommandQueue.Callbacks { 87 88 private final LockscreenGestureLogger mLockscreenGestureLogger = 89 Dependency.get(LockscreenGestureLogger.class); 90 91 private static final String TAG = "StatusBarNotificationPresenter"; 92 93 private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); 94 private final KeyguardStateController mKeyguardStateController; 95 private final NotificationViewHierarchyManager mViewHierarchyManager = 96 Dependency.get(NotificationViewHierarchyManager.class); 97 private final NotificationLockscreenUserManager mLockscreenUserManager = 98 Dependency.get(NotificationLockscreenUserManager.class); 99 private final SysuiStatusBarStateController mStatusBarStateController = 100 (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); 101 private final NotificationEntryManager mEntryManager = 102 Dependency.get(NotificationEntryManager.class); 103 private final NotificationMediaManager mMediaManager = 104 Dependency.get(NotificationMediaManager.class); 105 private final VisualStabilityManager mVisualStabilityManager = 106 Dependency.get(VisualStabilityManager.class); 107 private final NotificationGutsManager mGutsManager = 108 Dependency.get(NotificationGutsManager.class); 109 110 private final NotificationPanelViewController mNotificationPanel; 111 private final HeadsUpManagerPhone mHeadsUpManager; 112 private final AboveShelfObserver mAboveShelfObserver; 113 private final DozeScrimController mDozeScrimController; 114 private final ScrimController mScrimController; 115 private final KeyguardIndicationController mKeyguardIndicationController; 116 private final StatusBar mStatusBar; 117 private final ShadeController mShadeController; 118 private final LockscreenShadeTransitionController mShadeTransitionController; 119 private final CommandQueue mCommandQueue; 120 121 private final AccessibilityManager mAccessibilityManager; 122 private final KeyguardManager mKeyguardManager; 123 private final NotificationShadeWindowController mNotificationShadeWindowController; 124 private final IStatusBarService mBarService; 125 private final DynamicPrivacyController mDynamicPrivacyController; 126 private boolean mReinflateNotificationsOnUserSwitched; 127 private boolean mDispatchUiModeChangeOnUserSwitched; 128 private TextView mNotificationPanelDebugText; 129 130 protected boolean mVrMode; 131 StatusBarNotificationPresenter(Context context, NotificationPanelViewController panel, HeadsUpManagerPhone headsUp, NotificationShadeWindowView statusBarWindow, NotificationStackScrollLayoutController stackScrollerController, DozeScrimController dozeScrimController, ScrimController scrimController, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, StatusBar statusBar, ShadeController shadeController, LockscreenShadeTransitionController shadeTransitionController, CommandQueue commandQueue, InitController initController, NotificationInterruptStateProvider notificationInterruptStateProvider)132 public StatusBarNotificationPresenter(Context context, 133 NotificationPanelViewController panel, 134 HeadsUpManagerPhone headsUp, 135 NotificationShadeWindowView statusBarWindow, 136 NotificationStackScrollLayoutController stackScrollerController, 137 DozeScrimController dozeScrimController, 138 ScrimController scrimController, 139 NotificationShadeWindowController notificationShadeWindowController, 140 DynamicPrivacyController dynamicPrivacyController, 141 KeyguardStateController keyguardStateController, 142 KeyguardIndicationController keyguardIndicationController, 143 StatusBar statusBar, 144 ShadeController shadeController, 145 LockscreenShadeTransitionController shadeTransitionController, 146 CommandQueue commandQueue, 147 InitController initController, 148 NotificationInterruptStateProvider notificationInterruptStateProvider) { 149 mKeyguardStateController = keyguardStateController; 150 mNotificationPanel = panel; 151 mHeadsUpManager = headsUp; 152 mDynamicPrivacyController = dynamicPrivacyController; 153 mKeyguardIndicationController = keyguardIndicationController; 154 // TODO: use KeyguardStateController#isOccluded to remove this dependency 155 mStatusBar = statusBar; 156 mShadeController = shadeController; 157 mShadeTransitionController = shadeTransitionController; 158 mCommandQueue = commandQueue; 159 mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView()); 160 mNotificationShadeWindowController = notificationShadeWindowController; 161 mAboveShelfObserver.setListener(statusBarWindow.findViewById( 162 R.id.notification_container_parent)); 163 mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 164 mDozeScrimController = dozeScrimController; 165 mScrimController = scrimController; 166 mKeyguardManager = context.getSystemService(KeyguardManager.class); 167 mBarService = IStatusBarService.Stub.asInterface( 168 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 169 170 IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( 171 Context.VR_SERVICE)); 172 if (vrManager != null) { 173 try { 174 vrManager.registerListener(mVrStateCallbacks); 175 } catch (RemoteException e) { 176 Slog.e(TAG, "Failed to register VR mode state listener: " + e); 177 } 178 } 179 NotificationRemoteInputManager remoteInputManager = 180 Dependency.get(NotificationRemoteInputManager.class); 181 remoteInputManager.setUpWithCallback( 182 Dependency.get(NotificationRemoteInputManager.Callback.class), 183 mNotificationPanel.createRemoteInputDelegate()); 184 remoteInputManager.getController().addCallback( 185 Dependency.get(NotificationShadeWindowController.class)); 186 187 initController.addPostInitTask(() -> { 188 NotificationEntryListener notificationEntryListener = new NotificationEntryListener() { 189 @Override 190 public void onEntryRemoved( 191 @Nullable NotificationEntry entry, 192 NotificationVisibility visibility, 193 boolean removedByUser, 194 int reason) { 195 StatusBarNotificationPresenter.this.onNotificationRemoved( 196 entry.getKey(), entry.getSbn(), reason); 197 if (removedByUser) { 198 maybeEndAmbientPulse(); 199 } 200 } 201 }; 202 203 mKeyguardIndicationController.init(); 204 mViewHierarchyManager.setUpWithPresenter(this, 205 stackScrollerController.getNotificationListContainer()); 206 mEntryManager.setUpWithPresenter(this); 207 mEntryManager.addNotificationEntryListener(notificationEntryListener); 208 mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); 209 mEntryManager.addNotificationLifetimeExtender(mGutsManager); 210 mEntryManager.addNotificationLifetimeExtenders( 211 remoteInputManager.getLifetimeExtenders()); 212 notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor); 213 mLockscreenUserManager.setUpWithPresenter(this); 214 mMediaManager.setUpWithPresenter(this); 215 mGutsManager.setUpWithPresenter(this, 216 stackScrollerController.getNotificationListContainer(), mCheckSaveListener, 217 mOnSettingsClickListener); 218 // ForegroundServiceNotificationListener adds its listener in its constructor 219 // but we need to request it here in order for it to be instantiated. 220 // TODO: figure out how to do this correctly once Dependency.get() is gone. 221 Dependency.get(ForegroundServiceNotificationListener.class); 222 223 onUserSwitched(mLockscreenUserManager.getCurrentUserId()); 224 }); 225 Dependency.get(ConfigurationController.class).addCallback(this); 226 } 227 228 @Override onDensityOrFontScaleChanged()229 public void onDensityOrFontScaleChanged() { 230 MessagingMessage.dropCache(); 231 MessagingGroup.dropCache(); 232 if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) { 233 updateNotificationsOnDensityOrFontScaleChanged(); 234 } else { 235 mReinflateNotificationsOnUserSwitched = true; 236 } 237 } 238 239 @Override onUiModeChanged()240 public void onUiModeChanged() { 241 if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) { 242 updateNotificationOnUiModeChanged(); 243 } else { 244 mDispatchUiModeChangeOnUserSwitched = true; 245 } 246 } 247 248 @Override onOverlayChanged()249 public void onOverlayChanged() { 250 onDensityOrFontScaleChanged(); 251 } 252 updateNotificationOnUiModeChanged()253 private void updateNotificationOnUiModeChanged() { 254 List<NotificationEntry> userNotifications = 255 mEntryManager.getActiveNotificationsForCurrentUser(); 256 for (int i = 0; i < userNotifications.size(); i++) { 257 NotificationEntry entry = userNotifications.get(i); 258 ExpandableNotificationRow row = entry.getRow(); 259 if (row != null) { 260 row.onUiModeChanged(); 261 } 262 } 263 } 264 updateNotificationsOnDensityOrFontScaleChanged()265 private void updateNotificationsOnDensityOrFontScaleChanged() { 266 List<NotificationEntry> userNotifications = 267 mEntryManager.getActiveNotificationsForCurrentUser(); 268 for (int i = 0; i < userNotifications.size(); i++) { 269 NotificationEntry entry = userNotifications.get(i); 270 entry.onDensityOrFontScaleChanged(); 271 boolean exposedGuts = entry.areGutsExposed(); 272 if (exposedGuts) { 273 mGutsManager.onDensityOrFontScaleChanged(entry); 274 } 275 } 276 } 277 278 @Override isCollapsing()279 public boolean isCollapsing() { 280 return mNotificationPanel.isCollapsing() 281 || mNotificationShadeWindowController.isLaunchingActivity(); 282 } 283 maybeEndAmbientPulse()284 private void maybeEndAmbientPulse() { 285 if (mNotificationPanel.hasPulsingNotifications() && 286 !mHeadsUpManager.hasNotifications()) { 287 // We were showing a pulse for a notification, but no notifications are pulsing anymore. 288 // Finish the pulse. 289 mDozeScrimController.pulseOutNow(); 290 } 291 } 292 293 @Override updateNotificationViews(final String reason)294 public void updateNotificationViews(final String reason) { 295 // The function updateRowStates depends on both of these being non-null, so check them here. 296 // We may be called before they are set from DeviceProvisionedController's callback. 297 if (mScrimController == null) return; 298 299 // Do not modify the notifications during collapse. 300 if (isCollapsing()) { 301 mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason)); 302 return; 303 } 304 305 mViewHierarchyManager.updateNotificationViews(); 306 307 mNotificationPanel.updateNotificationViews(reason); 308 } 309 onNotificationRemoved(String key, StatusBarNotification old, int reason)310 private void onNotificationRemoved(String key, StatusBarNotification old, int reason) { 311 if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); 312 313 if (old != null) { 314 if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() 315 && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { 316 if (mStatusBarStateController.getState() == StatusBarState.SHADE 317 && reason != NotificationListenerService.REASON_CLICK) { 318 mCommandQueue.animateCollapsePanels(); 319 } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED 320 && !isCollapsing()) { 321 mStatusBarStateController.setState(StatusBarState.KEYGUARD); 322 } 323 } 324 } 325 } 326 hasActiveNotifications()327 public boolean hasActiveNotifications() { 328 return mEntryManager.hasActiveNotifications(); 329 } 330 331 @Override onUserSwitched(int newUserId)332 public void onUserSwitched(int newUserId) { 333 // Begin old BaseStatusBar.userSwitched 334 mHeadsUpManager.setUser(newUserId); 335 // End old BaseStatusBar.userSwitched 336 if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId); 337 mCommandQueue.animateCollapsePanels(); 338 if (mReinflateNotificationsOnUserSwitched) { 339 updateNotificationsOnDensityOrFontScaleChanged(); 340 mReinflateNotificationsOnUserSwitched = false; 341 } 342 if (mDispatchUiModeChangeOnUserSwitched) { 343 updateNotificationOnUiModeChanged(); 344 mDispatchUiModeChangeOnUserSwitched = false; 345 } 346 updateNotificationViews("user switched"); 347 mMediaManager.clearCurrentMediaNotification(); 348 mStatusBar.setLockscreenUser(newUserId); 349 updateMediaMetaData(true, false); 350 } 351 352 @Override onBindRow(ExpandableNotificationRow row)353 public void onBindRow(ExpandableNotificationRow row) { 354 row.setAboveShelfChangedListener(mAboveShelfObserver); 355 row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen); 356 } 357 358 @Override isPresenterFullyCollapsed()359 public boolean isPresenterFullyCollapsed() { 360 return mNotificationPanel.isFullyCollapsed(); 361 } 362 363 @Override onActivated(ActivatableNotificationView view)364 public void onActivated(ActivatableNotificationView view) { 365 onActivated(); 366 if (view != null) mNotificationPanel.setActivatedChild(view); 367 } 368 onActivated()369 public void onActivated() { 370 mLockscreenGestureLogger.write( 371 MetricsEvent.ACTION_LS_NOTE, 372 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); 373 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH); 374 ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild(); 375 if (previousView != null) { 376 previousView.makeInactive(true /* animate */); 377 } 378 } 379 380 @Override onActivationReset(ActivatableNotificationView view)381 public void onActivationReset(ActivatableNotificationView view) { 382 if (view == mNotificationPanel.getActivatedChild()) { 383 mNotificationPanel.setActivatedChild(null); 384 mKeyguardIndicationController.hideTransientIndication(); 385 } 386 } 387 388 @Override updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)389 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { 390 mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); 391 } 392 393 @Override onUpdateRowStates()394 public void onUpdateRowStates() { 395 mNotificationPanel.onUpdateRowStates(); 396 } 397 398 @Override onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)399 public void onExpandClicked(NotificationEntry clickedEntry, View clickedView, 400 boolean nowExpanded) { 401 mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); 402 mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK"); 403 if (nowExpanded) { 404 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { 405 mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); 406 } else if (clickedEntry.isSensitive() 407 && mDynamicPrivacyController.isInLockedDownShade()) { 408 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 409 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */ 410 , null /* cancelRunnable */, false /* afterKeyguardGone */); 411 } 412 } 413 } 414 415 @Override isDeviceInVrMode()416 public boolean isDeviceInVrMode() { 417 return mVrMode; 418 } 419 onLockedNotificationImportanceChange(OnDismissAction dismissAction)420 private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) { 421 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 422 mActivityStarter.dismissKeyguardThenExecute(dismissAction, null, 423 true /* afterKeyguardGone */); 424 } 425 426 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 427 @Override 428 public void onVrStateChanged(boolean enabled) { 429 mVrMode = enabled; 430 } 431 }; 432 433 private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() { 434 @Override 435 public void checkSave(Runnable saveImportance, StatusBarNotification sbn) { 436 // If the user has security enabled, show challenge if the setting is changed. 437 if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier()) 438 && mKeyguardManager.isKeyguardLocked()) { 439 onLockedNotificationImportanceChange(() -> { 440 saveImportance.run(); 441 return true; 442 }); 443 } else { 444 saveImportance.run(); 445 } 446 } 447 }; 448 449 private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() { 450 @Override 451 public void onSettingsClick(String key) { 452 try { 453 mBarService.onNotificationSettingsViewed(key); 454 } catch (RemoteException e) { 455 // if we're here we're dead 456 } 457 } 458 }; 459 460 private final NotificationInterruptSuppressor mInterruptSuppressor = 461 new NotificationInterruptSuppressor() { 462 @Override 463 public String getName() { 464 return TAG; 465 } 466 467 @Override 468 public boolean suppressAwakeHeadsUp(NotificationEntry entry) { 469 final StatusBarNotification sbn = entry.getSbn(); 470 if (mStatusBar.isOccluded()) { 471 boolean devicePublic = mLockscreenUserManager 472 .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); 473 boolean userPublic = devicePublic 474 || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); 475 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); 476 if (userPublic && needsRedaction) { 477 // TODO(b/135046837): we can probably relax this with dynamic privacy 478 return true; 479 } 480 } 481 482 if (!mCommandQueue.panelsEnabled()) { 483 if (DEBUG) { 484 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey()); 485 } 486 return true; 487 } 488 489 if (sbn.getNotification().fullScreenIntent != null) { 490 // we don't allow head-up on the lockscreen (unless there's a 491 // "showWhenLocked" activity currently showing) if 492 // the potential HUN has a fullscreen intent 493 if (mKeyguardStateController.isShowing() && !mStatusBar.isOccluded()) { 494 if (DEBUG) { 495 Log.d(TAG, "No heads up: entry has fullscreen intent on lockscreen " 496 + sbn.getKey()); 497 } 498 return true; 499 } 500 501 if (mAccessibilityManager.isTouchExplorationEnabled()) { 502 if (DEBUG) { 503 Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey()); 504 } 505 return true; 506 } 507 } 508 return false; 509 } 510 511 @Override 512 public boolean suppressAwakeInterruptions(NotificationEntry entry) { 513 return isDeviceInVrMode(); 514 } 515 516 @Override 517 public boolean suppressInterruptions(NotificationEntry entry) { 518 return mStatusBar.areNotificationAlertsDisabled(); 519 } 520 }; 521 } 522