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