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.notification.interruption.VisualInterruptionType.BUBBLE; 18 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK; 19 import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE; 20 import static com.android.systemui.statusbar.phone.CentralSurfaces.CLOSE_PANEL_WHEN_EMPTIED; 21 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; 22 23 import android.app.KeyguardManager; 24 import android.content.Context; 25 import android.os.PowerManager; 26 import android.os.RemoteException; 27 import android.os.ServiceManager; 28 import android.service.notification.StatusBarNotification; 29 import android.service.vr.IVrManager; 30 import android.service.vr.IVrStateCallbacks; 31 import android.util.Log; 32 import android.util.Slog; 33 import android.view.View; 34 35 import androidx.annotation.NonNull; 36 37 import com.android.internal.statusbar.IStatusBarService; 38 import com.android.systemui.InitController; 39 import com.android.systemui.dagger.SysUISingleton; 40 import com.android.systemui.plugins.ActivityStarter; 41 import com.android.systemui.plugins.ActivityStarter.OnDismissAction; 42 import com.android.systemui.power.domain.interactor.PowerInteractor; 43 import com.android.systemui.res.R; 44 import com.android.systemui.shade.NotificationShadeWindowView; 45 import com.android.systemui.shade.QuickSettingsController; 46 import com.android.systemui.shade.ShadeViewController; 47 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; 48 import com.android.systemui.statusbar.CommandQueue; 49 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 50 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 51 import com.android.systemui.statusbar.NotificationMediaManager; 52 import com.android.systemui.statusbar.NotificationPresenter; 53 import com.android.systemui.statusbar.NotificationRemoteInputManager; 54 import com.android.systemui.statusbar.NotificationShadeWindowController; 55 import com.android.systemui.statusbar.StatusBarState; 56 import com.android.systemui.statusbar.SysuiStatusBarStateController; 57 import com.android.systemui.statusbar.notification.AboveShelfObserver; 58 import com.android.systemui.statusbar.notification.DynamicPrivacyController; 59 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 60 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; 61 import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor; 62 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; 63 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition; 64 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; 65 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter; 66 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; 67 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 68 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 69 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; 70 import com.android.systemui.statusbar.notification.stack.NotificationListContainer; 71 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; 72 import com.android.systemui.statusbar.policy.HeadsUpManager; 73 import com.android.systemui.statusbar.policy.KeyguardStateController; 74 75 import java.util.Set; 76 77 import javax.inject.Inject; 78 79 @SysUISingleton 80 class StatusBarNotificationPresenter implements NotificationPresenter, CommandQueue.Callbacks { 81 private static final String TAG = "StatusBarNotificationPresenter"; 82 83 private final ActivityStarter mActivityStarter; 84 private final KeyguardStateController mKeyguardStateController; 85 private final NotificationLockscreenUserManager mLockscreenUserManager; 86 private final SysuiStatusBarStateController mStatusBarStateController; 87 private final NotifShadeEventSource mNotifShadeEventSource; 88 private final NotificationMediaManager mMediaManager; 89 private final NotificationGutsManager mGutsManager; 90 private final ShadeViewController mNotificationPanel; 91 private final PanelExpansionInteractor mPanelExpansionInteractor; 92 private final HeadsUpManager mHeadsUpManager; 93 private final AboveShelfObserver mAboveShelfObserver; 94 private final DozeScrimController mDozeScrimController; 95 private final NotificationAlertsInteractor mNotificationAlertsInteractor; 96 private final NotificationStackScrollLayoutController mNsslController; 97 private final LockscreenShadeTransitionController mShadeTransitionController; 98 private final PowerInteractor mPowerInteractor; 99 private final CommandQueue mCommandQueue; 100 private final KeyguardManager mKeyguardManager; 101 private final NotificationShadeWindowController mNotificationShadeWindowController; 102 private final IStatusBarService mBarService; 103 private final DynamicPrivacyController mDynamicPrivacyController; 104 private final NotificationListContainer mNotifListContainer; 105 private final QuickSettingsController mQsController; 106 107 protected boolean mVrMode; 108 109 @Inject StatusBarNotificationPresenter( Context context, ShadeViewController panel, PanelExpansionInteractor panelExpansionInteractor, QuickSettingsController quickSettingsController, HeadsUpManager headsUp, NotificationShadeWindowView statusBarWindow, ActivityStarter activityStarter, NotificationStackScrollLayoutController stackScrollerController, DozeScrimController dozeScrimController, NotificationShadeWindowController notificationShadeWindowController, DynamicPrivacyController dynamicPrivacyController, KeyguardStateController keyguardStateController, NotificationAlertsInteractor notificationAlertsInteractor, LockscreenShadeTransitionController shadeTransitionController, PowerInteractor powerInteractor, CommandQueue commandQueue, NotificationLockscreenUserManager lockscreenUserManager, SysuiStatusBarStateController sysuiStatusBarStateController, NotifShadeEventSource notifShadeEventSource, NotificationMediaManager notificationMediaManager, NotificationGutsManager notificationGutsManager, InitController initController, VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, NotificationRemoteInputManager remoteInputManager, NotificationRemoteInputManager.Callback remoteInputManagerCallback, NotificationListContainer notificationListContainer)110 StatusBarNotificationPresenter( 111 Context context, 112 ShadeViewController panel, 113 PanelExpansionInteractor panelExpansionInteractor, 114 QuickSettingsController quickSettingsController, 115 HeadsUpManager headsUp, 116 NotificationShadeWindowView statusBarWindow, 117 ActivityStarter activityStarter, 118 NotificationStackScrollLayoutController stackScrollerController, 119 DozeScrimController dozeScrimController, 120 NotificationShadeWindowController notificationShadeWindowController, 121 DynamicPrivacyController dynamicPrivacyController, 122 KeyguardStateController keyguardStateController, 123 NotificationAlertsInteractor notificationAlertsInteractor, 124 LockscreenShadeTransitionController shadeTransitionController, 125 PowerInteractor powerInteractor, 126 CommandQueue commandQueue, 127 NotificationLockscreenUserManager lockscreenUserManager, 128 SysuiStatusBarStateController sysuiStatusBarStateController, 129 NotifShadeEventSource notifShadeEventSource, 130 NotificationMediaManager notificationMediaManager, 131 NotificationGutsManager notificationGutsManager, 132 InitController initController, 133 VisualInterruptionDecisionProvider visualInterruptionDecisionProvider, 134 NotificationRemoteInputManager remoteInputManager, 135 NotificationRemoteInputManager.Callback remoteInputManagerCallback, 136 NotificationListContainer notificationListContainer) { 137 mActivityStarter = activityStarter; 138 mKeyguardStateController = keyguardStateController; 139 mNotificationPanel = panel; 140 mPanelExpansionInteractor = panelExpansionInteractor; 141 mQsController = quickSettingsController; 142 mHeadsUpManager = headsUp; 143 mDynamicPrivacyController = dynamicPrivacyController; 144 mNotificationAlertsInteractor = notificationAlertsInteractor; 145 mNsslController = stackScrollerController; 146 mShadeTransitionController = shadeTransitionController; 147 mPowerInteractor = powerInteractor; 148 mCommandQueue = commandQueue; 149 mLockscreenUserManager = lockscreenUserManager; 150 mStatusBarStateController = sysuiStatusBarStateController; 151 mNotifShadeEventSource = notifShadeEventSource; 152 mMediaManager = notificationMediaManager; 153 mGutsManager = notificationGutsManager; 154 mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView()); 155 mNotificationShadeWindowController = notificationShadeWindowController; 156 mAboveShelfObserver.setListener(statusBarWindow.findViewById( 157 R.id.notification_container_parent)); 158 mDozeScrimController = dozeScrimController; 159 mKeyguardManager = context.getSystemService(KeyguardManager.class); 160 mBarService = IStatusBarService.Stub.asInterface( 161 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 162 mNotifListContainer = notificationListContainer; 163 164 IVrManager vrManager = IVrManager.Stub.asInterface(ServiceManager.getService( 165 Context.VR_SERVICE)); 166 if (vrManager != null) { 167 try { 168 vrManager.registerListener(mVrStateCallbacks); 169 } catch (RemoteException e) { 170 Slog.e(TAG, "Failed to register VR mode state listener: " + e); 171 } 172 } 173 remoteInputManager.setUpWithCallback( 174 remoteInputManagerCallback, 175 mNsslController.createDelegate()); 176 177 initController.addPostInitTask(() -> { 178 mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied); 179 mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse); 180 if (VisualInterruptionRefactor.isEnabled()) { 181 visualInterruptionDecisionProvider.addCondition(mAlertsDisabledCondition); 182 visualInterruptionDecisionProvider.addCondition(mVrModeCondition); 183 visualInterruptionDecisionProvider.addFilter(mNeedsRedactionFilter); 184 visualInterruptionDecisionProvider.addCondition(mPanelsDisabledCondition); 185 } else { 186 visualInterruptionDecisionProvider.addLegacySuppressor(mInterruptSuppressor); 187 } 188 mLockscreenUserManager.setUpWithPresenter(this); 189 mGutsManager.setUpWithPresenter( 190 this, mNotifListContainer, mOnSettingsClickListener); 191 192 onUserSwitched(mLockscreenUserManager.getCurrentUserId()); 193 }); 194 } 195 196 /** Called when the shade has been emptied to attempt to close the shade */ maybeClosePanelForShadeEmptied()197 private void maybeClosePanelForShadeEmptied() { 198 if (CLOSE_PANEL_WHEN_EMPTIED 199 && !mPanelExpansionInteractor.isTracking() 200 && !mQsController.getExpanded() 201 && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED 202 && !isCollapsing()) { 203 mStatusBarStateController.setState(StatusBarState.KEYGUARD); 204 } 205 } 206 207 @Override isCollapsing()208 public boolean isCollapsing() { 209 return mPanelExpansionInteractor.isCollapsing() 210 || mNotificationShadeWindowController.isLaunchingActivity(); 211 } 212 maybeEndAmbientPulse()213 private void maybeEndAmbientPulse() { 214 if (mNsslController.getNotificationListContainer().hasPulsingNotifications() 215 && !mHeadsUpManager.hasNotifications()) { 216 // We were showing a pulse for a notification, but no notifications are pulsing anymore. 217 // Finish the pulse. 218 mDozeScrimController.pulseOutNow(); 219 } 220 } 221 222 @Override onUserSwitched(int newUserId)223 public void onUserSwitched(int newUserId) { 224 // Begin old BaseStatusBar.userSwitched 225 mHeadsUpManager.setUser(newUserId); 226 // End old BaseStatusBar.userSwitched 227 mCommandQueue.animateCollapsePanels(); 228 mMediaManager.clearCurrentMediaNotification(); 229 } 230 231 @Override onBindRow(ExpandableNotificationRow row)232 public void onBindRow(ExpandableNotificationRow row) { 233 row.setAboveShelfChangedListener(mAboveShelfObserver); 234 row.setSecureStateProvider(mKeyguardStateController::canDismissLockScreen); 235 } 236 237 @Override isPresenterFullyCollapsed()238 public boolean isPresenterFullyCollapsed() { 239 return mPanelExpansionInteractor.isFullyCollapsed(); 240 } 241 242 @Override onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded)243 public void onExpandClicked(NotificationEntry clickedEntry, View clickedView, 244 boolean nowExpanded) { 245 mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); 246 mPowerInteractor.wakeUpIfDozing("NOTIFICATION_CLICK", PowerManager.WAKE_REASON_GESTURE); 247 if (nowExpanded) { 248 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) { 249 mShadeTransitionController.goToLockedShade(clickedEntry.getRow()); 250 } else if (clickedEntry.isSensitive().getValue() 251 && mDynamicPrivacyController.isInLockedDownShade()) { 252 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 253 mActivityStarter.dismissKeyguardThenExecute(() -> false /* dismissAction */ 254 , null /* cancelRunnable */, false /* afterKeyguardGone */); 255 } 256 } 257 } 258 259 @Override isDeviceInVrMode()260 public boolean isDeviceInVrMode() { 261 return mVrMode; 262 } 263 onLockedNotificationImportanceChange(OnDismissAction dismissAction)264 private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) { 265 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 266 mActivityStarter.dismissKeyguardThenExecute(dismissAction, null, 267 true /* afterKeyguardGone */); 268 } 269 270 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 271 @Override 272 public void onVrStateChanged(boolean enabled) { 273 mVrMode = enabled; 274 } 275 }; 276 277 private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() { 278 @Override 279 public void onSettingsClick(String key) { 280 try { 281 mBarService.onNotificationSettingsViewed(key); 282 } catch (RemoteException e) { 283 // if we're here we're dead 284 } 285 } 286 }; 287 288 private final NotificationInterruptSuppressor mInterruptSuppressor = 289 new NotificationInterruptSuppressor() { 290 @Override 291 public String getName() { 292 return TAG; 293 } 294 295 @Override 296 public boolean suppressAwakeHeadsUp(NotificationEntry entry) { 297 final StatusBarNotification sbn = entry.getSbn(); 298 if (mKeyguardStateController.isOccluded()) { 299 boolean devicePublic = mLockscreenUserManager 300 .isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); 301 boolean userPublic = devicePublic 302 || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); 303 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); 304 if (userPublic && needsRedaction) { 305 // TODO(b/135046837): we can probably relax this with dynamic privacy 306 return true; 307 } 308 } 309 310 if (!mCommandQueue.panelsEnabled()) { 311 if (DEBUG) { 312 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey()); 313 } 314 return true; 315 } 316 317 return false; 318 } 319 320 @Override 321 public boolean suppressAwakeInterruptions(NotificationEntry entry) { 322 return isDeviceInVrMode(); 323 } 324 325 @Override 326 public boolean suppressInterruptions(NotificationEntry entry) { 327 return !mNotificationAlertsInteractor.areNotificationAlertsEnabled(); 328 } 329 }; 330 331 private final VisualInterruptionCondition mAlertsDisabledCondition = 332 new VisualInterruptionCondition(Set.of(PEEK, PULSE, BUBBLE), 333 "notification alerts disabled") { 334 @Override 335 public boolean shouldSuppress() { 336 return !mNotificationAlertsInteractor.areNotificationAlertsEnabled(); 337 } 338 }; 339 340 private final VisualInterruptionCondition mVrModeCondition = 341 new VisualInterruptionCondition(Set.of(PEEK, BUBBLE), "device is in VR mode") { 342 @Override 343 public boolean shouldSuppress() { 344 return isDeviceInVrMode(); 345 } 346 }; 347 348 private final VisualInterruptionFilter mNeedsRedactionFilter = 349 new VisualInterruptionFilter(Set.of(PEEK), "needs redaction on public lockscreen") { 350 @Override 351 public boolean shouldSuppress(@NonNull NotificationEntry entry) { 352 if (!mKeyguardStateController.isOccluded()) { 353 return false; 354 } 355 356 if (!mLockscreenUserManager.needsRedaction(entry)) { 357 return false; 358 } 359 360 final int currentUserId = mLockscreenUserManager.getCurrentUserId(); 361 final boolean currentUserPublic = mLockscreenUserManager.isLockscreenPublicMode( 362 currentUserId); 363 364 final int notificationUserId = entry.getSbn().getUserId(); 365 final boolean notificationUserPublic = 366 mLockscreenUserManager.isLockscreenPublicMode(notificationUserId); 367 368 // TODO(b/135046837): we can probably relax this with dynamic privacy 369 return currentUserPublic || notificationUserPublic; 370 } 371 }; 372 373 private final VisualInterruptionCondition mPanelsDisabledCondition = 374 new VisualInterruptionCondition(Set.of(PEEK), "disabled panel") { 375 @Override 376 public boolean shouldSuppress() { 377 return !mCommandQueue.panelsEnabled(); 378 } 379 }; 380 } 381