1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 package com.android.systemui.statusbar; 17 18 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; 19 import static com.android.systemui.statusbar.NotificationRemoteInputManager 20 .FORCE_REMOTE_INPUT_HISTORY; 21 22 import android.app.Notification; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.content.Context; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.database.ContentObserver; 29 import android.os.Build; 30 import android.os.PowerManager; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.SystemClock; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.service.notification.NotificationListenerService; 37 import android.service.notification.NotificationStats; 38 import android.service.notification.StatusBarNotification; 39 import android.text.TextUtils; 40 import android.util.ArraySet; 41 import android.util.ArrayMap; 42 import android.util.EventLog; 43 import android.util.Log; 44 import android.view.View; 45 import android.view.ViewGroup; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.logging.MetricsLogger; 49 import com.android.internal.statusbar.IStatusBarService; 50 import com.android.internal.statusbar.NotificationVisibility; 51 import com.android.internal.util.NotificationMessagingUtil; 52 import com.android.systemui.DejankUtils; 53 import com.android.systemui.Dependency; 54 import com.android.systemui.Dumpable; 55 import com.android.systemui.EventLogTags; 56 import com.android.systemui.ForegroundServiceController; 57 import com.android.systemui.R; 58 import com.android.systemui.UiOffloadThread; 59 import com.android.systemui.recents.misc.SystemServicesProxy; 60 import com.android.systemui.statusbar.notification.InflationException; 61 import com.android.systemui.statusbar.notification.NotificationInflater; 62 import com.android.systemui.statusbar.notification.RowInflaterTask; 63 import com.android.systemui.statusbar.notification.VisualStabilityManager; 64 import com.android.systemui.statusbar.phone.NotificationGroupManager; 65 import com.android.systemui.statusbar.phone.StatusBar; 66 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 67 import com.android.systemui.statusbar.policy.HeadsUpManager; 68 import com.android.systemui.util.leak.LeakDetector; 69 70 import java.io.FileDescriptor; 71 import java.io.PrintWriter; 72 import java.util.ArrayList; 73 import java.util.HashMap; 74 import java.util.List; 75 import java.util.Map; 76 77 /** 78 * NotificationEntryManager is responsible for the adding, removing, and updating of notifications. 79 * It also handles tasks such as their inflation and their interaction with other 80 * Notification.*Manager objects. 81 */ 82 public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback, 83 ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler, 84 VisualStabilityManager.Callback { 85 private static final String TAG = "NotificationEntryMgr"; 86 protected static final boolean DEBUG = false; 87 protected static final boolean ENABLE_HEADS_UP = true; 88 protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up"; 89 90 protected final NotificationMessagingUtil mMessagingUtil; 91 protected final Context mContext; 92 protected final HashMap<String, NotificationData.Entry> mPendingNotifications = new HashMap<>(); 93 protected final NotificationClicker mNotificationClicker = new NotificationClicker(); 94 protected final ArraySet<NotificationData.Entry> mHeadsUpEntriesToRemoveOnSwitch = 95 new ArraySet<>(); 96 97 // Dependencies: 98 protected final NotificationLockscreenUserManager mLockscreenUserManager = 99 Dependency.get(NotificationLockscreenUserManager.class); 100 protected final NotificationGroupManager mGroupManager = 101 Dependency.get(NotificationGroupManager.class); 102 protected final NotificationGutsManager mGutsManager = 103 Dependency.get(NotificationGutsManager.class); 104 protected final NotificationRemoteInputManager mRemoteInputManager = 105 Dependency.get(NotificationRemoteInputManager.class); 106 protected final NotificationMediaManager mMediaManager = 107 Dependency.get(NotificationMediaManager.class); 108 protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); 109 protected final DeviceProvisionedController mDeviceProvisionedController = 110 Dependency.get(DeviceProvisionedController.class); 111 protected final VisualStabilityManager mVisualStabilityManager = 112 Dependency.get(VisualStabilityManager.class); 113 protected final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class); 114 protected final ForegroundServiceController mForegroundServiceController = 115 Dependency.get(ForegroundServiceController.class); 116 protected final NotificationListener mNotificationListener = 117 Dependency.get(NotificationListener.class); 118 private final SmartReplyController mSmartReplyController = 119 Dependency.get(SmartReplyController.class); 120 121 // A lifetime extender that watches for foreground service notifications 122 private final NotificationLifetimeExtender mFGSExtender = 123 new ForegroundServiceLifetimeExtender(); 124 private final Map<NotificationData.Entry, NotificationLifetimeExtender> mRetainedNotifications = 125 new ArrayMap<>(); 126 127 protected IStatusBarService mBarService; 128 protected NotificationPresenter mPresenter; 129 protected Callback mCallback; 130 protected PowerManager mPowerManager; 131 protected SystemServicesProxy mSystemServicesProxy; 132 protected NotificationListenerService.RankingMap mLatestRankingMap; 133 protected HeadsUpManager mHeadsUpManager; 134 protected NotificationData mNotificationData; 135 protected ContentObserver mHeadsUpObserver; 136 protected boolean mUseHeadsUp = false; 137 protected boolean mDisableNotificationAlerts; 138 protected NotificationListContainer mListContainer; 139 private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; 140 /** 141 * Notifications with keys in this set are not actually around anymore. We kept them around 142 * when they were canceled in response to a remote input interaction. This allows us to show 143 * what you replied and allows you to continue typing into it. 144 */ 145 private final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>(); 146 147 148 private final class NotificationClicker implements View.OnClickListener { 149 150 @Override onClick(final View v)151 public void onClick(final View v) { 152 if (!(v instanceof ExpandableNotificationRow)) { 153 Log.e(TAG, "NotificationClicker called on a view that is not a notification row."); 154 return; 155 } 156 157 mPresenter.wakeUpIfDozing(SystemClock.uptimeMillis(), v); 158 159 final ExpandableNotificationRow row = (ExpandableNotificationRow) v; 160 final StatusBarNotification sbn = row.getStatusBarNotification(); 161 if (sbn == null) { 162 Log.e(TAG, "NotificationClicker called on an unclickable notification,"); 163 return; 164 } 165 166 // Check if the notification is displaying the menu, if so slide notification back 167 if (row.getProvider() != null && row.getProvider().isMenuVisible()) { 168 row.animateTranslateNotification(0); 169 return; 170 } 171 172 // Mark notification for one frame. 173 row.setJustClicked(true); 174 DejankUtils.postAfterTraversal(() -> row.setJustClicked(false)); 175 176 mCallback.onNotificationClicked(sbn, row); 177 } 178 register(ExpandableNotificationRow row, StatusBarNotification sbn)179 public void register(ExpandableNotificationRow row, StatusBarNotification sbn) { 180 Notification notification = sbn.getNotification(); 181 if (notification.contentIntent != null || notification.fullScreenIntent != null) { 182 row.setOnClickListener(this); 183 } else { 184 row.setOnClickListener(null); 185 } 186 } 187 } 188 189 private final DeviceProvisionedController.DeviceProvisionedListener 190 mDeviceProvisionedListener = 191 new DeviceProvisionedController.DeviceProvisionedListener() { 192 @Override 193 public void onDeviceProvisionedChanged() { 194 updateNotifications(); 195 } 196 }; 197 getLatestRankingMap()198 public NotificationListenerService.RankingMap getLatestRankingMap() { 199 return mLatestRankingMap; 200 } 201 setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap)202 public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) { 203 mLatestRankingMap = latestRankingMap; 204 } 205 setDisableNotificationAlerts(boolean disableNotificationAlerts)206 public void setDisableNotificationAlerts(boolean disableNotificationAlerts) { 207 mDisableNotificationAlerts = disableNotificationAlerts; 208 mHeadsUpObserver.onChange(true); 209 } 210 destroy()211 public void destroy() { 212 mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener); 213 } 214 onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp)215 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { 216 if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) { 217 removeNotification(entry.key, getLatestRankingMap()); 218 mHeadsUpEntriesToRemoveOnSwitch.remove(entry); 219 if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) { 220 setLatestRankingMap(null); 221 } 222 } else { 223 updateNotificationRanking(null); 224 } 225 } 226 227 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)228 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 229 pw.println("NotificationEntryManager state:"); 230 pw.print(" mPendingNotifications="); 231 if (mPendingNotifications.size() == 0) { 232 pw.println("null"); 233 } else { 234 for (NotificationData.Entry entry : mPendingNotifications.values()) { 235 pw.println(entry.notification); 236 } 237 } 238 pw.println(" Lifetime-extended notifications:"); 239 if (mRetainedNotifications.isEmpty()) { 240 pw.println(" None"); 241 } else { 242 for (Map.Entry<NotificationData.Entry, NotificationLifetimeExtender> entry 243 : mRetainedNotifications.entrySet()) { 244 pw.println(" " + entry.getKey().notification + " retained by " 245 + entry.getValue().getClass().getName()); 246 } 247 } 248 pw.print(" mUseHeadsUp="); 249 pw.println(mUseHeadsUp); 250 pw.print(" mKeysKeptForRemoteInput: "); 251 pw.println(mKeysKeptForRemoteInput); 252 } 253 NotificationEntryManager(Context context)254 public NotificationEntryManager(Context context) { 255 mContext = context; 256 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 257 mBarService = IStatusBarService.Stub.asInterface( 258 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 259 mMessagingUtil = new NotificationMessagingUtil(context); 260 mSystemServicesProxy = SystemServicesProxy.getInstance(mContext); 261 mGroupManager.setPendingEntries(mPendingNotifications); 262 mFGSExtender.setCallback(key -> removeNotification(key, mLatestRankingMap)); 263 } 264 setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, Callback callback, HeadsUpManager headsUpManager)265 public void setUpWithPresenter(NotificationPresenter presenter, 266 NotificationListContainer listContainer, Callback callback, 267 HeadsUpManager headsUpManager) { 268 mPresenter = presenter; 269 mCallback = callback; 270 mNotificationData = new NotificationData(presenter); 271 mHeadsUpManager = headsUpManager; 272 mNotificationData.setHeadsUpManager(mHeadsUpManager); 273 mListContainer = listContainer; 274 275 mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) { 276 @Override 277 public void onChange(boolean selfChange) { 278 boolean wasUsing = mUseHeadsUp; 279 mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts 280 && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt( 281 mContext.getContentResolver(), 282 Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 283 Settings.Global.HEADS_UP_OFF); 284 Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); 285 if (wasUsing != mUseHeadsUp) { 286 if (!mUseHeadsUp) { 287 Log.d(TAG, 288 "dismissing any existing heads up notification on disable event"); 289 mHeadsUpManager.releaseAllImmediately(); 290 } 291 } 292 } 293 }; 294 295 if (ENABLE_HEADS_UP) { 296 mContext.getContentResolver().registerContentObserver( 297 Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED), 298 true, 299 mHeadsUpObserver); 300 mContext.getContentResolver().registerContentObserver( 301 Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, 302 mHeadsUpObserver); 303 } 304 305 mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); 306 307 mHeadsUpObserver.onChange(true); // set up 308 mOnAppOpsClickListener = mGutsManager::openGuts; 309 } 310 311 @VisibleForTesting 312 protected Map<NotificationData.Entry, NotificationLifetimeExtender> getRetainedNotificationMap()313 getRetainedNotificationMap() { 314 return mRetainedNotifications; 315 } 316 getNotificationData()317 public NotificationData getNotificationData() { 318 return mNotificationData; 319 } 320 getNotificationLongClicker()321 public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { 322 return mGutsManager::openGuts; 323 } 324 325 @Override logNotificationExpansion(String key, boolean userAction, boolean expanded)326 public void logNotificationExpansion(String key, boolean userAction, boolean expanded) { 327 mUiOffloadThread.submit(() -> { 328 try { 329 mBarService.onNotificationExpansionChanged(key, userAction, expanded); 330 } catch (RemoteException e) { 331 // Ignore. 332 } 333 }); 334 } 335 336 @Override onReorderingAllowed()337 public void onReorderingAllowed() { 338 updateNotifications(); 339 } 340 shouldSuppressFullScreenIntent(NotificationData.Entry entry)341 private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) { 342 if (mPresenter.isDeviceInVrMode()) { 343 return true; 344 } 345 346 return mNotificationData.shouldSuppressFullScreenIntent(entry); 347 } 348 inflateViews(NotificationData.Entry entry, ViewGroup parent)349 private void inflateViews(NotificationData.Entry entry, ViewGroup parent) { 350 PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, 351 entry.notification.getUser().getIdentifier()); 352 353 final StatusBarNotification sbn = entry.notification; 354 if (entry.row != null) { 355 entry.reset(); 356 updateNotification(entry, pmUser, sbn, entry.row); 357 } else { 358 new RowInflaterTask().inflate(mContext, parent, entry, 359 row -> { 360 bindRow(entry, pmUser, sbn, row); 361 updateNotification(entry, pmUser, sbn, row); 362 }); 363 } 364 } 365 bindRow(NotificationData.Entry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)366 private void bindRow(NotificationData.Entry entry, PackageManager pmUser, 367 StatusBarNotification sbn, ExpandableNotificationRow row) { 368 row.setExpansionLogger(this, entry.notification.getKey()); 369 row.setGroupManager(mGroupManager); 370 row.setHeadsUpManager(mHeadsUpManager); 371 row.setOnExpandClickListener(mPresenter); 372 row.setInflationCallback(this); 373 row.setLongPressListener(getNotificationLongClicker()); 374 mListContainer.bindRow(row); 375 mRemoteInputManager.bindRow(row); 376 377 // Get the app name. 378 // Note that Notification.Builder#bindHeaderAppName has similar logic 379 // but since this field is used in the guts, it must be accurate. 380 // Therefore we will only show the application label, or, failing that, the 381 // package name. No substitutions. 382 final String pkg = sbn.getPackageName(); 383 String appname = pkg; 384 try { 385 final ApplicationInfo info = pmUser.getApplicationInfo(pkg, 386 PackageManager.MATCH_UNINSTALLED_PACKAGES 387 | PackageManager.MATCH_DISABLED_COMPONENTS); 388 if (info != null) { 389 appname = String.valueOf(pmUser.getApplicationLabel(info)); 390 } 391 } catch (PackageManager.NameNotFoundException e) { 392 // Do nothing 393 } 394 row.setAppName(appname); 395 row.setOnDismissRunnable(() -> 396 performRemoveNotification(row.getStatusBarNotification())); 397 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 398 if (ENABLE_REMOTE_INPUT) { 399 row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); 400 } 401 402 row.setAppOpsOnClickListener(mOnAppOpsClickListener); 403 404 mCallback.onBindRow(entry, pmUser, sbn, row); 405 } 406 performRemoveNotification(StatusBarNotification n)407 public void performRemoveNotification(StatusBarNotification n) { 408 final int rank = mNotificationData.getRank(n.getKey()); 409 final int count = mNotificationData.getActiveNotifications().size(); 410 final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count, 411 true); 412 NotificationData.Entry entry = mNotificationData.get(n.getKey()); 413 414 if (FORCE_REMOTE_INPUT_HISTORY 415 && mKeysKeptForRemoteInput.contains(n.getKey())) { 416 mKeysKeptForRemoteInput.remove(n.getKey()); 417 } 418 419 mRemoteInputManager.onPerformRemoveNotification(n, entry); 420 final String pkg = n.getPackageName(); 421 final String tag = n.getTag(); 422 final int id = n.getId(); 423 final int userId = n.getUserId(); 424 try { 425 int dismissalSurface = NotificationStats.DISMISSAL_SHADE; 426 if (isHeadsUp(n.getKey())) { 427 dismissalSurface = NotificationStats.DISMISSAL_PEEK; 428 } else if (mListContainer.hasPulsingNotifications()) { 429 dismissalSurface = NotificationStats.DISMISSAL_AOD; 430 } 431 mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface, nv); 432 removeNotification(n.getKey(), null); 433 434 } catch (RemoteException ex) { 435 // system process is dead if we're here. 436 } 437 438 mCallback.onPerformRemoveNotification(n); 439 } 440 441 /** 442 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService 443 * about the failure. 444 * 445 * WARNING: this will call back into us. Don't hold any locks. 446 */ handleNotificationError(StatusBarNotification n, String message)447 void handleNotificationError(StatusBarNotification n, String message) { 448 removeNotification(n.getKey(), null); 449 try { 450 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(), 451 n.getInitialPid(), message, n.getUserId()); 452 } catch (RemoteException ex) { 453 // The end is nigh. 454 } 455 } 456 abortExistingInflation(String key)457 private void abortExistingInflation(String key) { 458 if (mPendingNotifications.containsKey(key)) { 459 NotificationData.Entry entry = mPendingNotifications.get(key); 460 entry.abortTask(); 461 mPendingNotifications.remove(key); 462 } 463 NotificationData.Entry addedEntry = mNotificationData.get(key); 464 if (addedEntry != null) { 465 addedEntry.abortTask(); 466 } 467 } 468 469 @Override handleInflationException(StatusBarNotification notification, Exception e)470 public void handleInflationException(StatusBarNotification notification, Exception e) { 471 handleNotificationError(notification, e.getMessage()); 472 } 473 addEntry(NotificationData.Entry shadeEntry)474 private void addEntry(NotificationData.Entry shadeEntry) { 475 boolean isHeadsUped = shouldPeek(shadeEntry); 476 if (isHeadsUped) { 477 mHeadsUpManager.showNotification(shadeEntry); 478 // Mark as seen immediately 479 setNotificationShown(shadeEntry.notification); 480 } 481 addNotificationViews(shadeEntry); 482 mCallback.onNotificationAdded(shadeEntry); 483 } 484 485 @Override onAsyncInflationFinished(NotificationData.Entry entry)486 public void onAsyncInflationFinished(NotificationData.Entry entry) { 487 mPendingNotifications.remove(entry.key); 488 // If there was an async task started after the removal, we don't want to add it back to 489 // the list, otherwise we might get leaks. 490 boolean isNew = mNotificationData.get(entry.key) == null; 491 if (isNew && !entry.row.isRemoved()) { 492 addEntry(entry); 493 } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) { 494 mVisualStabilityManager.onLowPriorityUpdated(entry); 495 mPresenter.updateNotificationViews(); 496 } 497 entry.row.setLowPriorityStateUpdated(false); 498 } 499 500 @Override removeNotification(String key, NotificationListenerService.RankingMap ranking)501 public void removeNotification(String key, NotificationListenerService.RankingMap ranking) { 502 // First chance to extend the lifetime of a notification 503 NotificationData.Entry pendingEntry = mPendingNotifications.get(key); 504 if (pendingEntry != null) { 505 if (mFGSExtender.shouldExtendLifetimeForPendingNotification(pendingEntry)) { 506 extendLifetime(pendingEntry, mFGSExtender); 507 return; 508 } 509 } 510 511 abortExistingInflation(key); 512 boolean deferRemoval = false; 513 if (mHeadsUpManager.isHeadsUp(key)) { 514 // A cancel() in response to a remote input shouldn't be delayed, as it makes the 515 // sending look longer than it takes. 516 // Also we should not defer the removal if reordering isn't allowed since otherwise 517 // some notifications can't disappear before the panel is closed. 518 boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key) 519 && !FORCE_REMOTE_INPUT_HISTORY 520 || !mVisualStabilityManager.isReorderingAllowed(); 521 deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime); 522 } 523 mMediaManager.onNotificationRemoved(key); 524 525 NotificationData.Entry entry = mNotificationData.get(key); 526 if (FORCE_REMOTE_INPUT_HISTORY 527 && shouldKeepForRemoteInput(entry) 528 && entry.row != null && !entry.row.isDismissed()) { 529 CharSequence remoteInputText = entry.remoteInputText; 530 if (TextUtils.isEmpty(remoteInputText)) { 531 remoteInputText = entry.remoteInputTextWhenReset; 532 } 533 StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry, 534 remoteInputText, false /* showSpinner */); 535 boolean updated = false; 536 entry.onRemoteInputInserted(); 537 try { 538 updateNotificationInternal(newSbn, null); 539 updated = true; 540 } catch (InflationException e) { 541 deferRemoval = false; 542 } 543 if (updated) { 544 Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key); 545 addKeyKeptForRemoteInput(entry.key); 546 return; 547 } 548 } 549 550 if (FORCE_REMOTE_INPUT_HISTORY 551 && shouldKeepForSmartReply(entry) 552 && entry.row != null && !entry.row.isDismissed()) { 553 // Turn off the spinner and hide buttons when an app cancels the notification. 554 StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry); 555 boolean updated = false; 556 try { 557 updateNotificationInternal(newSbn, null); 558 updated = true; 559 } catch (InflationException e) { 560 // Ignore just don't keep the notification around. 561 } 562 // Treat the reply as longer sending. 563 mSmartReplyController.stopSending(entry); 564 if (updated) { 565 Log.w(TAG, "Keeping notification around after sending smart reply " + entry.key); 566 addKeyKeptForRemoteInput(entry.key); 567 return; 568 } 569 } 570 571 if (entry != null && mFGSExtender.shouldExtendLifetime(entry)) { 572 extendLifetime(entry, mFGSExtender); 573 return; 574 } 575 576 // Actually removing notification so smart reply controller can forget about it. 577 mSmartReplyController.stopSending(entry); 578 579 if (deferRemoval) { 580 mLatestRankingMap = ranking; 581 mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key)); 582 return; 583 } 584 585 if (mRemoteInputManager.onRemoveNotification(entry)) { 586 mLatestRankingMap = ranking; 587 return; 588 } 589 590 if (entry != null && mGutsManager.getExposedGuts() != null 591 && mGutsManager.getExposedGuts() == entry.row.getGuts() 592 && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) { 593 Log.w(TAG, "Keeping notification because it's showing guts. " + key); 594 mLatestRankingMap = ranking; 595 mGutsManager.setKeyToRemoveOnGutsClosed(key); 596 return; 597 } 598 599 if (entry != null) { 600 mForegroundServiceController.removeNotification(entry.notification); 601 } 602 603 if (entry != null && entry.row != null) { 604 entry.row.setRemoved(); 605 mListContainer.cleanUpViewState(entry.row); 606 } 607 // Let's remove the children if this was a summary 608 handleGroupSummaryRemoved(key); 609 StatusBarNotification old = removeNotificationViews(key, ranking); 610 611 // Make sure no lifetime extension is happening anymore 612 cancelLifetimeExtension(entry); 613 mCallback.onNotificationRemoved(key, old); 614 } 615 extendLifetime( NotificationData.Entry entry, NotificationLifetimeExtender extender)616 private void extendLifetime( 617 NotificationData.Entry entry, NotificationLifetimeExtender extender) { 618 // Cancel any other extender which might be holding on to this notification entry 619 NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry); 620 if (activeExtender != null && activeExtender != extender) { 621 activeExtender.setShouldManageLifetime(entry, false); 622 } 623 624 mRetainedNotifications.put(entry, extender); 625 extender.setShouldManageLifetime(entry, true); 626 } 627 cancelLifetimeExtension(NotificationData.Entry entry)628 private void cancelLifetimeExtension(NotificationData.Entry entry) { 629 NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry); 630 if (activeExtender != null) { 631 activeExtender.setShouldManageLifetime(entry, false); 632 } 633 } 634 rebuildNotificationWithRemoteInput(NotificationData.Entry entry, CharSequence remoteInputText, boolean showSpinner)635 public StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry, 636 CharSequence remoteInputText, boolean showSpinner) { 637 StatusBarNotification sbn = entry.notification; 638 639 Notification.Builder b = Notification.Builder 640 .recoverBuilder(mContext, sbn.getNotification().clone()); 641 if (remoteInputText != null) { 642 CharSequence[] oldHistory = sbn.getNotification().extras 643 .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY); 644 CharSequence[] newHistory; 645 if (oldHistory == null) { 646 newHistory = new CharSequence[1]; 647 } else { 648 newHistory = new CharSequence[oldHistory.length + 1]; 649 System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length); 650 } 651 newHistory[0] = String.valueOf(remoteInputText); 652 b.setRemoteInputHistory(newHistory); 653 } 654 b.setShowRemoteInputSpinner(showSpinner); 655 b.setHideSmartReplies(true); 656 657 Notification newNotification = b.build(); 658 659 // Undo any compatibility view inflation 660 newNotification.contentView = sbn.getNotification().contentView; 661 newNotification.bigContentView = sbn.getNotification().bigContentView; 662 newNotification.headsUpContentView = sbn.getNotification().headsUpContentView; 663 664 StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(), 665 sbn.getOpPkg(), 666 sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), 667 newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime()); 668 return newSbn; 669 } 670 671 @VisibleForTesting rebuildNotificationForCanceledSmartReplies( NotificationData.Entry entry)672 StatusBarNotification rebuildNotificationForCanceledSmartReplies( 673 NotificationData.Entry entry) { 674 return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */, 675 false /* showSpinner */); 676 } 677 shouldKeepForSmartReply(NotificationData.Entry entry)678 private boolean shouldKeepForSmartReply(NotificationData.Entry entry) { 679 return entry != null && mSmartReplyController.isSendingSmartReply(entry.key); 680 } 681 shouldKeepForRemoteInput(NotificationData.Entry entry)682 private boolean shouldKeepForRemoteInput(NotificationData.Entry entry) { 683 if (entry == null) { 684 return false; 685 } 686 if (mRemoteInputManager.getController().isSpinning(entry.key)) { 687 return true; 688 } 689 if (entry.hasJustSentRemoteInput()) { 690 return true; 691 } 692 return false; 693 } 694 removeNotificationViews(String key, NotificationListenerService.RankingMap ranking)695 private StatusBarNotification removeNotificationViews(String key, 696 NotificationListenerService.RankingMap ranking) { 697 NotificationData.Entry entry = mNotificationData.remove(key, ranking); 698 if (entry == null) { 699 Log.w(TAG, "removeNotification for unknown key: " + key); 700 return null; 701 } 702 updateNotifications(); 703 Dependency.get(LeakDetector.class).trackGarbage(entry); 704 return entry.notification; 705 } 706 707 /** 708 * Ensures that the group children are cancelled immediately when the group summary is cancelled 709 * instead of waiting for the notification manager to send all cancels. Otherwise this could 710 * lead to flickers. 711 * 712 * This also ensures that the animation looks nice and only consists of a single disappear 713 * animation instead of multiple. 714 * @param key the key of the notification was removed 715 * 716 */ handleGroupSummaryRemoved(String key)717 private void handleGroupSummaryRemoved(String key) { 718 NotificationData.Entry entry = mNotificationData.get(key); 719 if (entry != null && entry.row != null 720 && entry.row.isSummaryWithChildren()) { 721 if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) { 722 // We don't want to remove children for autobundled notifications as they are not 723 // always cancelled. We only remove them if they were dismissed by the user. 724 return; 725 } 726 List<ExpandableNotificationRow> notificationChildren = 727 entry.row.getNotificationChildren(); 728 for (int i = 0; i < notificationChildren.size(); i++) { 729 ExpandableNotificationRow row = notificationChildren.get(i); 730 if ((row.getStatusBarNotification().getNotification().flags 731 & Notification.FLAG_FOREGROUND_SERVICE) != 0) { 732 // the child is a foreground service notification which we can't remove! 733 continue; 734 } 735 row.setKeepInParent(true); 736 // we need to set this state earlier as otherwise we might generate some weird 737 // animations 738 row.setRemoved(); 739 } 740 } 741 } 742 updateNotificationsOnDensityOrFontScaleChanged()743 public void updateNotificationsOnDensityOrFontScaleChanged() { 744 ArrayList<NotificationData.Entry> userNotifications = 745 mNotificationData.getNotificationsForCurrentUser(); 746 for (int i = 0; i < userNotifications.size(); i++) { 747 NotificationData.Entry entry = userNotifications.get(i); 748 boolean exposedGuts = mGutsManager.getExposedGuts() != null 749 && entry.row.getGuts() == mGutsManager.getExposedGuts(); 750 entry.row.onDensityOrFontScaleChanged(); 751 if (exposedGuts) { 752 mGutsManager.onDensityOrFontScaleChanged(entry.row); 753 } 754 } 755 } 756 updateNotification(NotificationData.Entry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)757 protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser, 758 StatusBarNotification sbn, ExpandableNotificationRow row) { 759 row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry)); 760 boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey()); 761 boolean isUpdate = mNotificationData.get(entry.key) != null; 762 boolean wasLowPriority = row.isLowPriority(); 763 row.setIsLowPriority(isLowPriority); 764 row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority)); 765 // bind the click event to the content area 766 mNotificationClicker.register(row, sbn); 767 768 // Extract target SDK version. 769 try { 770 ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0); 771 entry.targetSdk = info.targetSdkVersion; 772 } catch (PackageManager.NameNotFoundException ex) { 773 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex); 774 } 775 row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD 776 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); 777 entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP); 778 entry.autoRedacted = entry.notification.getNotification().publicVersion == null; 779 780 entry.row = row; 781 entry.row.setOnActivatedListener(mPresenter); 782 783 boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn, 784 mNotificationData.getImportance(sbn.getKey())); 785 boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight 786 && !mPresenter.isPresenterFullyCollapsed(); 787 row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); 788 row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); 789 row.updateNotification(entry); 790 } 791 792 addNotificationViews(NotificationData.Entry entry)793 protected void addNotificationViews(NotificationData.Entry entry) { 794 if (entry == null) { 795 return; 796 } 797 // Add the expanded view and icon. 798 mNotificationData.add(entry); 799 tagForeground(entry.notification); 800 updateNotifications(); 801 } 802 createNotificationViews(StatusBarNotification sbn)803 protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) 804 throws InflationException { 805 if (DEBUG) { 806 Log.d(TAG, "createNotificationViews(notification=" + sbn); 807 } 808 NotificationData.Entry entry = new NotificationData.Entry(sbn); 809 Dependency.get(LeakDetector.class).trackInstance(entry); 810 entry.createIcons(mContext, sbn); 811 // Construct the expanded view. 812 inflateViews(entry, mListContainer.getViewParentForNotification(entry)); 813 return entry; 814 } 815 addNotificationInternal(StatusBarNotification notification, NotificationListenerService.RankingMap ranking)816 private void addNotificationInternal(StatusBarNotification notification, 817 NotificationListenerService.RankingMap ranking) throws InflationException { 818 String key = notification.getKey(); 819 if (DEBUG) Log.d(TAG, "addNotification key=" + key); 820 821 mNotificationData.updateRanking(ranking); 822 NotificationData.Entry shadeEntry = createNotificationViews(notification); 823 boolean isHeadsUped = shouldPeek(shadeEntry); 824 if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) { 825 if (shouldSuppressFullScreenIntent(shadeEntry)) { 826 if (DEBUG) { 827 Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key); 828 } 829 } else if (mNotificationData.getImportance(key) 830 < NotificationManager.IMPORTANCE_HIGH) { 831 if (DEBUG) { 832 Log.d(TAG, "No Fullscreen intent: not important enough: " 833 + key); 834 } 835 } else { 836 // Stop screensaver if the notification has a fullscreen intent. 837 // (like an incoming phone call) 838 SystemServicesProxy.getInstance(mContext).awakenDreamsAsync(); 839 840 // not immersive & a fullscreen alert should be shown 841 if (DEBUG) 842 Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); 843 try { 844 EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, 845 key); 846 notification.getNotification().fullScreenIntent.send(); 847 shadeEntry.notifyFullScreenIntentLaunched(); 848 mMetricsLogger.count("note_fullscreen", 1); 849 } catch (PendingIntent.CanceledException e) { 850 } 851 } 852 } 853 abortExistingInflation(key); 854 855 mForegroundServiceController.addNotification(notification, 856 mNotificationData.getImportance(key)); 857 858 mPendingNotifications.put(key, shadeEntry); 859 mGroupManager.onPendingEntryAdded(shadeEntry); 860 } 861 862 @VisibleForTesting tagForeground(StatusBarNotification notification)863 protected void tagForeground(StatusBarNotification notification) { 864 ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps( 865 notification.getUserId(), notification.getPackageName()); 866 if (activeOps != null) { 867 int N = activeOps.size(); 868 for (int i = 0; i < N; i++) { 869 updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(), 870 notification.getPackageName(), true); 871 } 872 } 873 } 874 875 @Override addNotification(StatusBarNotification notification, NotificationListenerService.RankingMap ranking)876 public void addNotification(StatusBarNotification notification, 877 NotificationListenerService.RankingMap ranking) { 878 try { 879 addNotificationInternal(notification, ranking); 880 } catch (InflationException e) { 881 handleInflationException(notification, e); 882 } 883 } 884 updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon)885 public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) { 886 String foregroundKey = mForegroundServiceController.getStandardLayoutKey( 887 UserHandle.getUserId(uid), pkg); 888 if (foregroundKey != null) { 889 mNotificationData.updateAppOp(appOp, uid, pkg, foregroundKey, showIcon); 890 updateNotifications(); 891 } 892 } 893 alertAgain(NotificationData.Entry oldEntry, Notification newNotification)894 private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) { 895 return oldEntry == null || !oldEntry.hasInterrupted() 896 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0; 897 } 898 updateNotificationInternal(StatusBarNotification notification, NotificationListenerService.RankingMap ranking)899 private void updateNotificationInternal(StatusBarNotification notification, 900 NotificationListenerService.RankingMap ranking) throws InflationException { 901 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); 902 903 final String key = notification.getKey(); 904 abortExistingInflation(key); 905 NotificationData.Entry entry = mNotificationData.get(key); 906 if (entry == null) { 907 return; 908 } 909 mHeadsUpEntriesToRemoveOnSwitch.remove(entry); 910 mRemoteInputManager.onUpdateNotification(entry); 911 mSmartReplyController.stopSending(entry); 912 913 if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) { 914 mGutsManager.setKeyToRemoveOnGutsClosed(null); 915 Log.w(TAG, "Notification that was kept for guts was updated. " + key); 916 } 917 918 // No need to keep the lifetime extension around if an update comes in 919 cancelLifetimeExtension(entry); 920 921 Notification n = notification.getNotification(); 922 mNotificationData.updateRanking(ranking); 923 924 final StatusBarNotification oldNotification = entry.notification; 925 entry.notification = notification; 926 mGroupManager.onEntryUpdated(entry, oldNotification); 927 928 entry.updateIcons(mContext, notification); 929 inflateViews(entry, mListContainer.getViewParentForNotification(entry)); 930 931 mForegroundServiceController.updateNotification(notification, 932 mNotificationData.getImportance(key)); 933 934 boolean shouldPeek = shouldPeek(entry, notification); 935 boolean alertAgain = alertAgain(entry, n); 936 937 updateHeadsUp(key, entry, shouldPeek, alertAgain); 938 updateNotifications(); 939 940 if (!notification.isClearable()) { 941 // The user may have performed a dismiss action on the notification, since it's 942 // not clearable we should snap it back. 943 mListContainer.snapViewIfNeeded(entry.row); 944 } 945 946 if (DEBUG) { 947 // Is this for you? 948 boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification); 949 Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you"); 950 } 951 952 mCallback.onNotificationUpdated(notification); 953 } 954 955 @Override updateNotification(StatusBarNotification notification, NotificationListenerService.RankingMap ranking)956 public void updateNotification(StatusBarNotification notification, 957 NotificationListenerService.RankingMap ranking) { 958 try { 959 updateNotificationInternal(notification, ranking); 960 } catch (InflationException e) { 961 handleInflationException(notification, e); 962 } 963 } 964 updateNotifications()965 public void updateNotifications() { 966 mNotificationData.filterAndSort(); 967 968 mPresenter.updateNotificationViews(); 969 } 970 updateNotificationRanking(NotificationListenerService.RankingMap ranking)971 public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) { 972 mNotificationData.updateRanking(ranking); 973 updateNotifications(); 974 } 975 shouldPeek(NotificationData.Entry entry)976 protected boolean shouldPeek(NotificationData.Entry entry) { 977 return shouldPeek(entry, entry.notification); 978 } 979 shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn)980 public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) { 981 if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) { 982 if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode"); 983 return false; 984 } 985 986 if (mNotificationData.shouldFilterOut(entry)) { 987 if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey()); 988 return false; 989 } 990 991 boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming(); 992 993 if (!inUse && !mPresenter.isDozing()) { 994 if (DEBUG) { 995 Log.d(TAG, "No peeking: not in use: " + sbn.getKey()); 996 } 997 return false; 998 } 999 1000 if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(entry)) { 1001 if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); 1002 return false; 1003 } 1004 1005 // Peeking triggers an ambient display pulse, so disable peek is ambient is active 1006 if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(entry)) { 1007 if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey()); 1008 return false; 1009 } 1010 1011 if (entry.hasJustLaunchedFullScreenIntent()) { 1012 if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey()); 1013 return false; 1014 } 1015 1016 if (isSnoozedPackage(sbn)) { 1017 if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey()); 1018 return false; 1019 } 1020 1021 // Allow peeking for DEFAULT notifications only if we're on Ambient Display. 1022 int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT 1023 : NotificationManager.IMPORTANCE_HIGH; 1024 if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) { 1025 if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey()); 1026 return false; 1027 } 1028 1029 // Don't peek notifications that are suppressed due to group alert behavior 1030 if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { 1031 if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior"); 1032 return false; 1033 } 1034 1035 if (!mCallback.shouldPeek(entry, sbn)) { 1036 return false; 1037 } 1038 1039 return true; 1040 } 1041 setNotificationShown(StatusBarNotification n)1042 protected void setNotificationShown(StatusBarNotification n) { 1043 setNotificationsShown(new String[]{n.getKey()}); 1044 } 1045 setNotificationsShown(String[] keys)1046 protected void setNotificationsShown(String[] keys) { 1047 try { 1048 mNotificationListener.setNotificationsShown(keys); 1049 } catch (RuntimeException e) { 1050 Log.d(TAG, "failed setNotificationsShown: ", e); 1051 } 1052 } 1053 isSnoozedPackage(StatusBarNotification sbn)1054 protected boolean isSnoozedPackage(StatusBarNotification sbn) { 1055 return mHeadsUpManager.isSnoozed(sbn.getPackageName()); 1056 } 1057 updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek, boolean alertAgain)1058 protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek, 1059 boolean alertAgain) { 1060 final boolean wasHeadsUp = isHeadsUp(key); 1061 if (wasHeadsUp) { 1062 if (!shouldPeek) { 1063 // We don't want this to be interrupting anymore, lets remove it 1064 mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */); 1065 } else { 1066 mHeadsUpManager.updateNotification(entry, alertAgain); 1067 } 1068 } else if (shouldPeek && alertAgain) { 1069 // This notification was updated to be a heads-up, show it! 1070 mHeadsUpManager.showNotification(entry); 1071 } 1072 } 1073 isHeadsUp(String key)1074 protected boolean isHeadsUp(String key) { 1075 return mHeadsUpManager.isHeadsUp(key); 1076 } 1077 isNotificationKeptForRemoteInput(String key)1078 public boolean isNotificationKeptForRemoteInput(String key) { 1079 return mKeysKeptForRemoteInput.contains(key); 1080 } 1081 removeKeyKeptForRemoteInput(String key)1082 public void removeKeyKeptForRemoteInput(String key) { 1083 mKeysKeptForRemoteInput.remove(key); 1084 } 1085 addKeyKeptForRemoteInput(String key)1086 public void addKeyKeptForRemoteInput(String key) { 1087 if (FORCE_REMOTE_INPUT_HISTORY) { 1088 mKeysKeptForRemoteInput.add(key); 1089 } 1090 } 1091 1092 /** 1093 * Callback for NotificationEntryManager. 1094 */ 1095 public interface Callback { 1096 1097 /** 1098 * Called when a new entry is created. 1099 * 1100 * @param shadeEntry entry that was created 1101 */ onNotificationAdded(NotificationData.Entry shadeEntry)1102 void onNotificationAdded(NotificationData.Entry shadeEntry); 1103 1104 /** 1105 * Called when a notification was updated. 1106 * 1107 * @param notification notification that was updated 1108 */ onNotificationUpdated(StatusBarNotification notification)1109 void onNotificationUpdated(StatusBarNotification notification); 1110 1111 /** 1112 * Called when a notification was removed. 1113 * 1114 * @param key key of notification that was removed 1115 * @param old StatusBarNotification of the notification before it was removed 1116 */ onNotificationRemoved(String key, StatusBarNotification old)1117 void onNotificationRemoved(String key, StatusBarNotification old); 1118 1119 1120 /** 1121 * Called when a notification is clicked. 1122 * 1123 * @param sbn notification that was clicked 1124 * @param row row for that notification 1125 */ onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row)1126 void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row); 1127 1128 /** 1129 * Called when a new notification and row is created. 1130 * 1131 * @param entry entry for the notification 1132 * @param pmUser package manager for user 1133 * @param sbn notification 1134 * @param row row for the notification 1135 */ onBindRow(NotificationData.Entry entry, PackageManager pmUser, StatusBarNotification sbn, ExpandableNotificationRow row)1136 void onBindRow(NotificationData.Entry entry, PackageManager pmUser, 1137 StatusBarNotification sbn, ExpandableNotificationRow row); 1138 1139 /** 1140 * Removes a notification immediately. 1141 * 1142 * @param statusBarNotification notification that is being removed 1143 */ onPerformRemoveNotification(StatusBarNotification statusBarNotification)1144 void onPerformRemoveNotification(StatusBarNotification statusBarNotification); 1145 1146 /** 1147 * Returns true if NotificationEntryManager should peek this notification. 1148 * 1149 * @param entry entry of the notification that might be peeked 1150 * @param sbn notification that might be peeked 1151 * @return true if the notification should be peeked 1152 */ shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn)1153 boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn); 1154 } 1155 } 1156