• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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