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.SysUiServiceProvider.getComponent; 18 import static com.android.systemui.statusbar.phone.StatusBar.CLOSE_PANEL_WHEN_EMPTIED; 19 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG; 20 import static com.android.systemui.statusbar.phone.StatusBar.MULTIUSER_DEBUG; 21 import static com.android.systemui.statusbar.phone.StatusBar.SPEW; 22 23 import android.annotation.Nullable; 24 import android.app.KeyguardManager; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 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.ViewGroup; 36 import android.view.accessibility.AccessibilityManager; 37 import android.widget.TextView; 38 39 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 40 import com.android.internal.statusbar.IStatusBarService; 41 import com.android.internal.statusbar.NotificationVisibility; 42 import com.android.internal.widget.MessagingGroup; 43 import com.android.internal.widget.MessagingMessage; 44 import com.android.keyguard.KeyguardUpdateMonitor; 45 import com.android.systemui.Dependency; 46 import com.android.systemui.ForegroundServiceNotificationListener; 47 import com.android.systemui.InitController; 48 import com.android.systemui.R; 49 import com.android.systemui.plugins.ActivityStarter; 50 import com.android.systemui.plugins.ActivityStarter.OnDismissAction; 51 import com.android.systemui.plugins.statusbar.StatusBarStateController; 52 import com.android.systemui.statusbar.AmbientPulseManager; 53 import com.android.systemui.statusbar.CommandQueue; 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.NotificationViewHierarchyManager; 59 import com.android.systemui.statusbar.StatusBarState; 60 import com.android.systemui.statusbar.SysuiStatusBarStateController; 61 import com.android.systemui.statusbar.notification.AboveShelfObserver; 62 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; 63 import com.android.systemui.statusbar.notification.NotificationAlertingManager; 64 import com.android.systemui.statusbar.notification.NotificationEntryListener; 65 import com.android.systemui.statusbar.notification.NotificationEntryManager; 66 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; 67 import com.android.systemui.statusbar.notification.VisualStabilityManager; 68 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 69 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; 70 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; 71 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 72 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 73 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; 74 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; 75 import com.android.systemui.statusbar.notification.stack.NotificationListContainer; 76 import com.android.systemui.statusbar.policy.ConfigurationController; 77 import com.android.systemui.statusbar.policy.KeyguardMonitor; 78 79 import java.util.ArrayList; 80 81 public class StatusBarNotificationPresenter implements NotificationPresenter, 82 ConfigurationController.ConfigurationListener, 83 NotificationRowBinderImpl.BindRowCallback { 84 85 private final LockscreenGestureLogger mLockscreenGestureLogger = 86 Dependency.get(LockscreenGestureLogger.class); 87 88 private static final String TAG = "StatusBarNotificationPresenter"; 89 90 private final ShadeController mShadeController = Dependency.get(ShadeController.class); 91 private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class); 92 private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); 93 private final NotificationViewHierarchyManager mViewHierarchyManager = 94 Dependency.get(NotificationViewHierarchyManager.class); 95 private final NotificationLockscreenUserManager mLockscreenUserManager = 96 Dependency.get(NotificationLockscreenUserManager.class); 97 private final SysuiStatusBarStateController mStatusBarStateController = 98 (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class); 99 private final NotificationEntryManager mEntryManager = 100 Dependency.get(NotificationEntryManager.class); 101 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider = 102 Dependency.get(NotificationInterruptionStateProvider.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 protected AmbientPulseManager mAmbientPulseManager = Dependency.get(AmbientPulseManager.class); 110 111 private final NotificationPanelView mNotificationPanel; 112 private final HeadsUpManagerPhone mHeadsUpManager; 113 private final AboveShelfObserver mAboveShelfObserver; 114 private final DozeScrimController mDozeScrimController; 115 private final ScrimController mScrimController; 116 private final Context mContext; 117 private final CommandQueue mCommandQueue; 118 119 private final AccessibilityManager mAccessibilityManager; 120 private final KeyguardManager mKeyguardManager; 121 private final ActivityLaunchAnimator mActivityLaunchAnimator; 122 private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 123 private final int mMaxAllowedKeyguardNotifications; 124 private final IStatusBarService mBarService; 125 private boolean mReinflateNotificationsOnUserSwitched; 126 private boolean mDispatchUiModeChangeOnUserSwitched; 127 private final UnlockMethodCache mUnlockMethodCache; 128 private TextView mNotificationPanelDebugText; 129 130 protected boolean mVrMode; 131 private int mMaxKeyguardNotifications; 132 StatusBarNotificationPresenter(Context context, NotificationPanelView panel, HeadsUpManagerPhone headsUp, StatusBarWindowView statusBarWindow, ViewGroup stackScroller, DozeScrimController dozeScrimController, ScrimController scrimController, ActivityLaunchAnimator activityLaunchAnimator, StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationAlertingManager notificationAlertingManager, NotificationRowBinderImpl notificationRowBinder)133 public StatusBarNotificationPresenter(Context context, 134 NotificationPanelView panel, 135 HeadsUpManagerPhone headsUp, 136 StatusBarWindowView statusBarWindow, 137 ViewGroup stackScroller, 138 DozeScrimController dozeScrimController, 139 ScrimController scrimController, 140 ActivityLaunchAnimator activityLaunchAnimator, 141 StatusBarKeyguardViewManager statusBarKeyguardViewManager, 142 NotificationAlertingManager notificationAlertingManager, 143 NotificationRowBinderImpl notificationRowBinder) { 144 mContext = context; 145 mNotificationPanel = panel; 146 mHeadsUpManager = headsUp; 147 mCommandQueue = getComponent(context, CommandQueue.class); 148 mAboveShelfObserver = new AboveShelfObserver(stackScroller); 149 mActivityLaunchAnimator = activityLaunchAnimator; 150 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 151 mAboveShelfObserver.setListener(statusBarWindow.findViewById( 152 R.id.notification_container_parent)); 153 mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 154 mDozeScrimController = dozeScrimController; 155 mScrimController = scrimController; 156 mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); 157 mKeyguardManager = context.getSystemService(KeyguardManager.class); 158 mMaxAllowedKeyguardNotifications = context.getResources().getInteger( 159 R.integer.keyguard_max_notification_count); 160 mBarService = IStatusBarService.Stub.asInterface( 161 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 162 163 if (MULTIUSER_DEBUG) { 164 mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info); 165 mNotificationPanelDebugText.setVisibility(View.VISIBLE); 166 } 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 NotificationRemoteInputManager remoteInputManager = 178 Dependency.get(NotificationRemoteInputManager.class); 179 remoteInputManager.setUpWithCallback( 180 Dependency.get(NotificationRemoteInputManager.Callback.class), 181 mNotificationPanel.createRemoteInputDelegate()); 182 remoteInputManager.getController().addCallback( 183 Dependency.get(StatusBarWindowController.class)); 184 185 NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller; 186 Dependency.get(InitController.class).addPostInitTask(() -> { 187 NotificationEntryListener notificationEntryListener = new NotificationEntryListener() { 188 @Override 189 public void onNotificationAdded(NotificationEntry entry) { 190 // Recalculate the position of the sliding windows and the titles. 191 mShadeController.updateAreThereNotifications(); 192 } 193 194 @Override 195 public void onPostEntryUpdated(NotificationEntry entry) { 196 mShadeController.updateAreThereNotifications(); 197 } 198 199 @Override 200 public void onEntryRemoved( 201 @Nullable NotificationEntry entry, 202 NotificationVisibility visibility, 203 boolean removedByUser) { 204 StatusBarNotificationPresenter.this.onNotificationRemoved( 205 entry.key, entry.notification); 206 if (removedByUser) { 207 maybeEndAmbientPulse(); 208 } 209 } 210 }; 211 212 mViewHierarchyManager.setUpWithPresenter(this, notifListContainer); 213 mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager); 214 mEntryManager.addNotificationEntryListener(notificationEntryListener); 215 mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager); 216 mEntryManager.addNotificationLifetimeExtender(mAmbientPulseManager); 217 mEntryManager.addNotificationLifetimeExtender(mGutsManager); 218 mEntryManager.addNotificationLifetimeExtenders( 219 remoteInputManager.getLifetimeExtenders()); 220 notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, 221 mEntryManager, this); 222 mNotificationInterruptionStateProvider.setUpWithPresenter( 223 this, mHeadsUpManager, this::canHeadsUp); 224 mLockscreenUserManager.setUpWithPresenter(this); 225 mMediaManager.setUpWithPresenter(this); 226 mVisualStabilityManager.setUpWithPresenter(this); 227 mGutsManager.setUpWithPresenter(this, 228 notifListContainer, mCheckSaveListener, mOnSettingsClickListener); 229 // ForegroundServiceNotificationListener adds its listener in its constructor 230 // but we need to request it here in order for it to be instantiated. 231 // TODO: figure out how to do this correctly once Dependency.get() is gone. 232 Dependency.get(ForegroundServiceNotificationListener.class); 233 234 onUserSwitched(mLockscreenUserManager.getCurrentUserId()); 235 }); 236 Dependency.get(ConfigurationController.class).addCallback(this); 237 238 notificationAlertingManager.setHeadsUpManager(mHeadsUpManager); 239 } 240 241 @Override onDensityOrFontScaleChanged()242 public void onDensityOrFontScaleChanged() { 243 MessagingMessage.dropCache(); 244 MessagingGroup.dropCache(); 245 if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { 246 updateNotificationsOnDensityOrFontScaleChanged(); 247 } else { 248 mReinflateNotificationsOnUserSwitched = true; 249 } 250 } 251 252 @Override onUiModeChanged()253 public void onUiModeChanged() { 254 if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { 255 updateNotificationOnUiModeChanged(); 256 } else { 257 mDispatchUiModeChangeOnUserSwitched = true; 258 } 259 } 260 261 @Override onOverlayChanged()262 public void onOverlayChanged() { 263 onDensityOrFontScaleChanged(); 264 } 265 updateNotificationOnUiModeChanged()266 private void updateNotificationOnUiModeChanged() { 267 ArrayList<NotificationEntry> userNotifications 268 = mEntryManager.getNotificationData().getNotificationsForCurrentUser(); 269 for (int i = 0; i < userNotifications.size(); i++) { 270 NotificationEntry entry = userNotifications.get(i); 271 ExpandableNotificationRow row = entry.getRow(); 272 if (row != null) { 273 row.onUiModeChanged(); 274 } 275 } 276 } 277 updateNotificationsOnDensityOrFontScaleChanged()278 private void updateNotificationsOnDensityOrFontScaleChanged() { 279 ArrayList<NotificationEntry> userNotifications = 280 mEntryManager.getNotificationData().getNotificationsForCurrentUser(); 281 for (int i = 0; i < userNotifications.size(); i++) { 282 NotificationEntry entry = userNotifications.get(i); 283 entry.onDensityOrFontScaleChanged(); 284 boolean exposedGuts = entry.areGutsExposed(); 285 if (exposedGuts) { 286 mGutsManager.onDensityOrFontScaleChanged(entry); 287 } 288 } 289 } 290 291 @Override isCollapsing()292 public boolean isCollapsing() { 293 return mNotificationPanel.isCollapsing() 294 || mActivityLaunchAnimator.isAnimationPending() 295 || mActivityLaunchAnimator.isAnimationRunning(); 296 } 297 maybeEndAmbientPulse()298 private void maybeEndAmbientPulse() { 299 if (mNotificationPanel.hasPulsingNotifications() && 300 !mAmbientPulseManager.hasNotifications()) { 301 // We were showing a pulse for a notification, but no notifications are pulsing anymore. 302 // Finish the pulse. 303 mDozeScrimController.pulseOutNow(); 304 } 305 } 306 307 @Override updateNotificationViews()308 public void updateNotificationViews() { 309 // The function updateRowStates depends on both of these being non-null, so check them here. 310 // We may be called before they are set from DeviceProvisionedController's callback. 311 if (mScrimController == null) return; 312 313 // Do not modify the notifications during collapse. 314 if (isCollapsing()) { 315 mShadeController.addPostCollapseAction(this::updateNotificationViews); 316 return; 317 } 318 319 mViewHierarchyManager.updateNotificationViews(); 320 321 mNotificationPanel.updateNotificationViews(); 322 } 323 onNotificationRemoved(String key, StatusBarNotification old)324 public void onNotificationRemoved(String key, StatusBarNotification old) { 325 if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); 326 327 if (old != null) { 328 if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications() 329 && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) { 330 if (mStatusBarStateController.getState() == StatusBarState.SHADE) { 331 mCommandQueue.animateCollapsePanels(); 332 } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED 333 && !isCollapsing()) { 334 mShadeController.goToKeyguard(); 335 } 336 } 337 } 338 mShadeController.updateAreThereNotifications(); 339 } 340 hasActiveNotifications()341 public boolean hasActiveNotifications() { 342 return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); 343 } 344 canHeadsUp(NotificationEntry entry, StatusBarNotification sbn)345 public boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn) { 346 if (mShadeController.isDozing()) { 347 return false; 348 } 349 350 if (mShadeController.isOccluded()) { 351 boolean devicePublic = mLockscreenUserManager. 352 isLockscreenPublicMode(mLockscreenUserManager.getCurrentUserId()); 353 boolean userPublic = devicePublic 354 || mLockscreenUserManager.isLockscreenPublicMode(sbn.getUserId()); 355 boolean needsRedaction = mLockscreenUserManager.needsRedaction(entry); 356 if (userPublic && needsRedaction) { 357 return false; 358 } 359 } 360 361 if (!mCommandQueue.panelsEnabled()) { 362 if (DEBUG) { 363 Log.d(TAG, "No heads up: disabled panel : " + sbn.getKey()); 364 } 365 return false; 366 } 367 368 if (sbn.getNotification().fullScreenIntent != null) { 369 if (mAccessibilityManager.isTouchExplorationEnabled()) { 370 if (DEBUG) Log.d(TAG, "No heads up: accessible fullscreen: " + sbn.getKey()); 371 return false; 372 } else { 373 // we only allow head-up on the lockscreen if it doesn't have a fullscreen intent 374 return !mKeyguardMonitor.isShowing() 375 || mShadeController.isOccluded(); 376 } 377 } 378 return true; 379 } 380 381 @Override onUserSwitched(int newUserId)382 public void onUserSwitched(int newUserId) { 383 // Begin old BaseStatusBar.userSwitched 384 mHeadsUpManager.setUser(newUserId); 385 // End old BaseStatusBar.userSwitched 386 if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); 387 mCommandQueue.animateCollapsePanels(); 388 if (mReinflateNotificationsOnUserSwitched) { 389 updateNotificationsOnDensityOrFontScaleChanged(); 390 mReinflateNotificationsOnUserSwitched = false; 391 } 392 if (mDispatchUiModeChangeOnUserSwitched) { 393 updateNotificationOnUiModeChanged(); 394 mDispatchUiModeChangeOnUserSwitched = false; 395 } 396 updateNotificationViews(); 397 mMediaManager.clearCurrentMediaNotification(); 398 mShadeController.setLockscreenUser(newUserId); 399 updateMediaMetaData(true, false); 400 } 401 402 @Override onBindRow(NotificationEntry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)403 public void onBindRow(NotificationEntry entry, PackageManager pmUser, 404 StatusBarNotification sbn, ExpandableNotificationRow row) { 405 row.setAboveShelfChangedListener(mAboveShelfObserver); 406 row.setSecureStateProvider(mUnlockMethodCache::canSkipBouncer); 407 } 408 409 @Override isPresenterFullyCollapsed()410 public boolean isPresenterFullyCollapsed() { 411 return mNotificationPanel.isFullyCollapsed(); 412 } 413 414 @Override onActivated(ActivatableNotificationView view)415 public void onActivated(ActivatableNotificationView view) { 416 onActivated(); 417 if (view != null) mNotificationPanel.setActivatedChild(view); 418 } 419 onActivated()420 public void onActivated() { 421 mLockscreenGestureLogger.write( 422 MetricsEvent.ACTION_LS_NOTE, 423 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); 424 mNotificationPanel.showTransientIndication(R.string.notification_tap_again); 425 ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild(); 426 if (previousView != null) { 427 previousView.makeInactive(true /* animate */); 428 } 429 } 430 431 @Override onActivationReset(ActivatableNotificationView view)432 public void onActivationReset(ActivatableNotificationView view) { 433 if (view == mNotificationPanel.getActivatedChild()) { 434 mNotificationPanel.setActivatedChild(null); 435 mShadeController.onActivationReset(); 436 } 437 } 438 439 @Override updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation)440 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { 441 mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); 442 } 443 444 @Override getMaxNotificationsWhileLocked(boolean recompute)445 public int getMaxNotificationsWhileLocked(boolean recompute) { 446 if (recompute) { 447 mMaxKeyguardNotifications = Math.max(1, 448 mNotificationPanel.computeMaxKeyguardNotifications( 449 mMaxAllowedKeyguardNotifications)); 450 return mMaxKeyguardNotifications; 451 } 452 return mMaxKeyguardNotifications; 453 } 454 455 @Override onUpdateRowStates()456 public void onUpdateRowStates() { 457 mNotificationPanel.onUpdateRowStates(); 458 } 459 460 @Override onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded)461 public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) { 462 mHeadsUpManager.setExpanded(clickedEntry, nowExpanded); 463 if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && nowExpanded) { 464 mShadeController.goToLockedShade(clickedEntry.getRow()); 465 } 466 } 467 468 @Override isDeviceInVrMode()469 public boolean isDeviceInVrMode() { 470 return mVrMode; 471 } 472 473 @Override isPresenterLocked()474 public boolean isPresenterLocked() { 475 return mStatusBarKeyguardViewManager.isShowing() 476 && mStatusBarKeyguardViewManager.isSecure(); 477 } 478 onLockedNotificationImportanceChange(OnDismissAction dismissAction)479 private void onLockedNotificationImportanceChange(OnDismissAction dismissAction) { 480 mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); 481 mActivityStarter.dismissKeyguardThenExecute(dismissAction, null, 482 true /* afterKeyguardGone */); 483 } 484 485 private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { 486 @Override 487 public void onVrStateChanged(boolean enabled) { 488 mVrMode = enabled; 489 } 490 }; 491 492 private final CheckSaveListener mCheckSaveListener = new CheckSaveListener() { 493 @Override 494 public void checkSave(Runnable saveImportance, StatusBarNotification sbn) { 495 // If the user has security enabled, show challenge if the setting is changed. 496 if (mLockscreenUserManager.isLockscreenPublicMode(sbn.getUser().getIdentifier()) 497 && mKeyguardManager.isKeyguardLocked()) { 498 onLockedNotificationImportanceChange(() -> { 499 saveImportance.run(); 500 return true; 501 }); 502 } else { 503 saveImportance.run(); 504 } 505 } 506 }; 507 508 private final OnSettingsClickListener mOnSettingsClickListener = new OnSettingsClickListener() { 509 @Override 510 public void onSettingsClick(String key) { 511 try { 512 mBarService.onNotificationSettingsViewed(key); 513 } catch (RemoteException e) { 514 // if we're here we're dead 515 } 516 } 517 }; 518 } 519