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.CentralSurfaces.CLOSE_PANEL_WHEN_EMPTIED; 18 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; 19 import static com.android.systemui.statusbar.phone.CentralSurfaces.MULTIUSER_DEBUG; 20 21 import android.app.KeyguardManager; 22 import android.content.Context; 23 import android.os.PowerManager; 24 import android.os.RemoteException; 25 import android.os.ServiceManager; 26 import android.os.SystemClock; 27 import android.service.notification.StatusBarNotification; 28 import android.service.vr.IVrManager; 29 import android.service.vr.IVrStateCallbacks; 30 import android.util.Log; 31 import android.util.Slog; 32 import android.view.View; 33 import android.view.accessibility.AccessibilityManager; 34 35 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 36 import com.android.internal.statusbar.IStatusBarService; 37 import com.android.systemui.InitController; 38 import com.android.systemui.R; 39 import com.android.systemui.plugins.ActivityStarter; 40 import com.android.systemui.plugins.ActivityStarter.OnDismissAction; 41 import com.android.systemui.shade.NotificationPanelViewController; 42 import com.android.systemui.shade.NotificationShadeWindowView; 43 import com.android.systemui.shade.QuickSettingsController; 44 import com.android.systemui.statusbar.CommandQueue; 45 import com.android.systemui.statusbar.KeyguardIndicationController; 46 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 47 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 48 import com.android.systemui.statusbar.NotificationMediaManager; 49 import com.android.systemui.statusbar.NotificationPresenter; 50 import com.android.systemui.statusbar.NotificationRemoteInputManager; 51 import com.android.systemui.statusbar.NotificationShadeWindowController; 52 import com.android.systemui.statusbar.StatusBarState; 53 import com.android.systemui.statusbar.SysuiStatusBarStateController; 54 import com.android.systemui.statusbar.notification.AboveShelfObserver; 55 import com.android.systemui.statusbar.notification.DynamicPrivacyController; 56 import com.android.systemui.statusbar.notification.NotifPipelineFlags; 57 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 58 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; 59 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; 60 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; 61 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; 62 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; 63 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 64 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 65 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; 66 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; 67 import com.android.systemui.statusbar.notification.stack.NotificationListContainer; 68 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; 69 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; 70 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; 71 import com.android.systemui.statusbar.policy.KeyguardStateController; 72 73 import javax.inject.Inject; 74 75 @CentralSurfacesComponent.CentralSurfacesScope 76 class StatusBarNotificationPresenter implements NotificationPresenter, 77 NotificationRowBinderImpl.BindRowCallback, 78 CommandQueue.Callbacks { 79 private static final String TAG = "StatusBarNotificationPresenter"; 80 81 private final ActivityStarter mActivityStarter; 82 private final KeyguardStateController mKeyguardStateController; 83 private final NotificationLockscreenUserManager mLockscreenUserManager; 84 private final SysuiStatusBarStateController mStatusBarStateController; 85 private final NotifShadeEventSource mNotifShadeEventSource; 86 private final NotificationMediaManager mMediaManager; 87 private final NotificationGutsManager mGutsManager; 88 private final LockscreenGestureLogger mLockscreenGestureLogger; 89 90 private final NotificationPanelViewController mNotificationPanel; 91 private final HeadsUpManagerPhone mHeadsUpManager; 92 private final AboveShelfObserver mAboveShelfObserver; 93 private final DozeScrimController mDozeScrimController; 94 private final KeyguardIndicationController mKeyguardIndicationController; 95 private final CentralSurfaces mCentralSurfaces; 96 private final LockscreenShadeTransitionController mShadeTransitionController; 97 private final CommandQueue mCommandQueue; 98 99 private final AccessibilityManager mAccessibilityManager; 100 private final KeyguardManager mKeyguardManager; 101 private final NotificationShadeWindowController mNotificationShadeWindowController; 102 private final NotifPipelineFlags mNotifPipelineFlags; 103 private final IStatusBarService mBarService; 104 private final DynamicPrivacyController mDynamicPrivacyController; 105 private final NotificationListContainer mNotifListContainer; 106 private final QuickSettingsController mQsController; 107 108 protected boolean mVrMode; 109 110 @Inject StatusBarNotificationPresenter( Context context, NotificationPanelViewController panel, QuickSettingsController quickSettingsController, HeadsUpManagerPhone headsUp, NotificationShadeWindowView statusBarWindow, ActivityStarter activityStarter, NotificationStackScrollLayoutController stackScrollerController, DozeScrimController dozeScrimController, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, KeyguardIndicationController keyguardIndicationController, CentralSurfaces centralSurfaces, LockscreenShadeTransitionController shadeTransitionController, CommandQueue commandQueue, NotificationLockscreenUserManager lockscreenUserManager, SysuiStatusBarStateController sysuiStatusBarStateController, NotifShadeEventSource notifShadeEventSource, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, LockscreenGestureLogger lockscreenGestureLogger, InitController initController, NotificationInterruptStateProvider notificationInterruptStateProvider, NotificationRemoteInputManager remoteInputManager, NotifPipelineFlags notifPipelineFlags, NotificationRemoteInputManager.Callback remoteInputManagerCallback, NotificationListContainer notificationListContainer)111 StatusBarNotificationPresenter( 112 Context context, 113 NotificationPanelViewController panel, 114 QuickSettingsController quickSettingsController, 115 HeadsUpManagerPhone headsUp, 116 NotificationShadeWindowView statusBarWindow, 117 ActivityStarter activityStarter, 118 NotificationStackScrollLayoutController stackScrollerController, 119 DozeScrimController dozeScrimController, 120 NotificationShadeWindowController notificationShadeWindowController, 121 DynamicPrivacyController dynamicPrivacyController, 122 KeyguardStateController keyguardStateController, 123 KeyguardIndicationController keyguardIndicationController, 124 CentralSurfaces centralSurfaces, 125 LockscreenShadeTransitionController shadeTransitionController, 126 CommandQueue commandQueue, 127 NotificationLockscreenUserManager lockscreenUserManager, 128 SysuiStatusBarStateController sysuiStatusBarStateController, 129 NotifShadeEventSource notifShadeEventSource, 130 NotificationMediaManager notificationMediaManager, 131 NotificationGutsManager notificationGutsManager, 132 LockscreenGestureLogger lockscreenGestureLogger, 133 InitController initController, 134 NotificationInterruptStateProvider notificationInterruptStateProvider, 135 NotificationRemoteInputManager remoteInputManager, 136 NotifPipelineFlags notifPipelineFlags, 137 NotificationRemoteInputManager.Callback remoteInputManagerCallback, 138 NotificationListContainer notificationListContainer) { 139 mActivityStarter = activityStarter; 140 mKeyguardStateController = keyguardStateController; 141 mNotificationPanel = panel; 142 mQsController = quickSettingsController; 143 mHeadsUpManager = headsUp; 144 mDynamicPrivacyController = dynamicPrivacyController; 145 mKeyguardIndicationController = keyguardIndicationController; 146 // TODO: use KeyguardStateController#isOccluded to remove this dependency 147 mCentralSurfaces = centralSurfaces; 148 mShadeTransitionController = shadeTransitionController; 149 mCommandQueue = commandQueue; 150 mLockscreenUserManager = lockscreenUserManager; 151 mStatusBarStateController = sysuiStatusBarStateController; 152 mNotifShadeEventSource = notifShadeEventSource; 153 mMediaManager = notificationMediaManager; 154 mGutsManager = notificationGutsManager; 155 mLockscreenGestureLogger = lockscreenGestureLogger; 156 mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView()); 157 mNotificationShadeWindowController = notificationShadeWindowController; 158 mNotifPipelineFlags = notifPipelineFlags; 159 mAboveShelfObserver.setListener(statusBarWindow.findViewById( 160 R.id.notification_container_parent)); 161 mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 162 mDozeScrimController = dozeScrimController; 163 mKeyguardManager = context.getSystemService(KeyguardManager.class); 164 mBarService = IStatusBarService.Stub.asInterface( 165 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 166 mNotifListContainer = notificationListContainer; 167 168 IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( 169 Context.VR_SERVICE)); 170 if (vrManager != null) { 171 try { 172 vrManager.registerListener(mVrStateCallbacks); 173 } catch (RemoteException e) { 174 Slog.e(TAG, "Failed to register VR mode state listener: " + e); 175 } 176 } 177 remoteInputManager.setUpWithCallback( 178 remoteInputManagerCallback, 179 mNotificationPanel.createRemoteInputDelegate()); 180 181 initController.addPostInitTask(() -> { 182 mKeyguardIndicationController.init(); 183 mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); 184 mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse); 185 notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor); 186 mLockscreenUserManager.setUpWithPresenter(this); 187 mGutsManager.setUpWithPresenter( 188 this, mNotifListContainer, mOnSettingsClickListener); 189 190 onUserSwitched(mLockscreenUserManager.getCurrentUserId()); 191 }); 192 } 193 194 /** Called when the shade has been emptied to attempt to close the shade */ maybeClosePanelForShadeEmptied()195 private void maybeClosePanelForShadeEmptied() { 196 if (CLOSE_PANEL_WHEN_EMPTIED 197 && !mNotificationPanel.isTracking() 198 && !mQsController.getExpanded() 199 && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED 200 && !isCollapsing()) { 201 mStatusBarStateController.setState(StatusBarState.KEYGUARD); 202 } 203 } 204 205 @Override isCollapsing()206 public boolean isCollapsing() { 207 return mNotificationPanel.isCollapsing() 208 || mNotificationShadeWindowController.isLaunchingActivity(); 209 } 210 maybeEndAmbientPulse()211 private void maybeEndAmbientPulse() { 212 if (mNotificationPanel.hasPulsingNotifications() && 213 !mHeadsUpManager.hasNotifications()) { 214 // We were showing a pulse for a notification, but no notifications are pulsing anymore. 215 // Finish the pulse. 216 mDozeScrimController.pulseOutNow(); 217 } 218 } 219 220 @Override onUserSwitched(int newUserId)221 public void onUserSwitched(int newUserId) { 222 // Begin old BaseStatusBar.userSwitched 223 mHeadsUpManager.setUser(newUserId); 224 // End old BaseStatusBar.userSwitched 225 if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId); 226 mCommandQueue.animateCollapsePanels(); 227 mMediaManager.clearCurrentMediaNotification(); 228 mCentralSurfaces.setLockscreenUser(newUserId); 229 updateMediaMetaData(true, false); 230 } 231 232 @Override onBindRow(ExpandableNotificationRow row)233 public void onBindRow(ExpandableNotificationRow row) { 234 row.setAboveShelfChangedListener(mAboveShelfObserver); 235 row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen); 236 } 237 238 @Override isPresenterFullyCollapsed()239 public boolean isPresenterFullyCollapsed() { 240 return mNotificationPanel.isFullyCollapsed(); 241 } 242 243 @Override onActivated(ActivatableNotificationView view)244 public void onActivated(ActivatableNotificationView view) { 245 onActivated(); 246 if (view != null) mNotificationPanel.setActivatedChild(view); 247 } 248 onActivated()249 public void onActivated() { 250 mLockscreenGestureLogger.write( 251 MetricsEvent.ACTION_LS_NOTE, 252 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); 253 mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH); 254 ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild(); 255 if (previousView != null) { 256 previousView.makeInactive(true /* animate */); 257 } 258 } 259 260 @Override onActivationReset(ActivatableNotificationView view)261 public void onActivationReset(ActivatableNotificationView view) { 262 if (view == mNotificationPanel.getActivatedChild()) { 263 mNotificationPanel.setActivatedChild(null); 264 mKeyguardIndicationController.hideTransientIndication(); 265 } 266 } 267 268 @Override updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)269 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { 270 mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); 271 } 272 273 @Override onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)274 public void onExpandClicked(NotificationEntry clickedEntry, View clickedView, 275 boolean nowExpanded) { 276 mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); 277 mCentralSurfaces.wakeUpIfDozing( 278 SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK", 279 PowerManager.WAKE_REASON_GESTURE); 280 if (nowExpanded) { 281 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { 282 mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); 283 } else if (clickedEntry.isSensitive() 284 && mDynamicPrivacyController.isInLockedDownShade()) { 285 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 286 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */ 287 , null /* cancelRunnable */, false /* afterKeyguardGone */); 288 } 289 } 290 } 291 292 @Override isDeviceInVrMode()293 public boolean isDeviceInVrMode() { 294 return mVrMode; 295 } 296 onLockedNotificationImportanceChange(OnDismissAction dismissAction)297 private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) { 298 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 299 mActivityStarter.dismissKeyguardThenExecute(dismissAction, null, 300 true /* afterKeyguardGone */); 301 } 302 303 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 304 @Override 305 public void onVrStateChanged(boolean enabled) { 306 mVrMode = enabled; 307 } 308 }; 309 310 private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() { 311 @Override 312 public void checkSave(Runnable saveImportance, StatusBarNotification sbn) { 313 // If the user has security enabled, show challenge if the setting is changed. 314 if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier()) 315 && mKeyguardManager.isKeyguardLocked()) { 316 onLockedNotificationImportanceChange(() -> { 317 saveImportance.run(); 318 return true; 319 }); 320 } else { 321 saveImportance.run(); 322 } 323 } 324 }; 325 326 private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() { 327 @Override 328 public void onSettingsClick(String key) { 329 try { 330 mBarService.onNotificationSettingsViewed(key); 331 } catch (RemoteException e) { 332 // if we're here we're dead 333 } 334 } 335 }; 336 337 private final NotificationInterruptSuppressor mInterruptSuppressor = 338 new NotificationInterruptSuppressor() { 339 @Override 340 public String getName() { 341 return TAG; 342 } 343 344 @Override 345 public boolean suppressAwakeHeadsUp(NotificationEntry entry) { 346 final StatusBarNotification sbn = entry.getSbn(); 347 if (mCentralSurfaces.isOccluded()) { 348 boolean devicePublic = mLockscreenUserManager 349 .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); 350 boolean userPublic = devicePublic 351 || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); 352 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); 353 if (userPublic && needsRedaction) { 354 // TODO(b/135046837): we can probably relax this with dynamic privacy 355 return true; 356 } 357 } 358 359 if (!mCommandQueue.panelsEnabled()) { 360 if (DEBUG) { 361 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey()); 362 } 363 return true; 364 } 365 366 if (sbn.getNotification().fullScreenIntent != null 367 && !mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()) { 368 // we don't allow head-up on the lockscreen (unless there's a 369 // "showWhenLocked" activity currently showing) if 370 // the potential HUN has a fullscreen intent 371 if (mKeyguardStateController.isShowing() && !mCentralSurfaces.isOccluded()) { 372 if (DEBUG) { 373 Log.d(TAG, "No heads up: entry has fullscreen intent on lockscreen " 374 + sbn.getKey()); 375 } 376 return true; 377 } 378 379 if (mAccessibilityManager.isTouchExplorationEnabled()) { 380 if (DEBUG) { 381 Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey()); 382 } 383 return true; 384 } 385 } 386 return false; 387 } 388 389 @Override 390 public boolean suppressAwakeInterruptions(NotificationEntry entry) { 391 return isDeviceInVrMode(); 392 } 393 394 @Override 395 public boolean suppressInterruptions(NotificationEntry entry) { 396 return mCentralSurfaces.areNotificationAlertsDisabled(); 397 } 398 }; 399 } 400