blob: 8ac4d300817920c3624d22748a5f2d1bb3b56413 [file] [log] [blame]
Ned Burnsf098dbf2019-09-13 19:17:53 -04001/*
2 * Copyright (C) 2019 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
17package com.android.systemui.statusbar.notification.collection;
18
19import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
20import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
21import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
22import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
23import static android.service.notification.NotificationListenerService.REASON_CLICK;
24import static android.service.notification.NotificationListenerService.REASON_ERROR;
25import static android.service.notification.NotificationListenerService.REASON_GROUP_OPTIMIZATION;
26import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
27import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
28import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
29import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
30import static android.service.notification.NotificationListenerService.REASON_PACKAGE_CHANGED;
31import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
32import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
33import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
34import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
35import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
36import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
37
Ned Burnsf098dbf2019-09-13 19:17:53 -040038import android.annotation.IntDef;
39import android.annotation.MainThread;
40import android.annotation.NonNull;
41import android.annotation.Nullable;
42import android.os.RemoteException;
43import android.service.notification.NotificationListenerService.Ranking;
44import android.service.notification.NotificationListenerService.RankingMap;
45import android.service.notification.StatusBarNotification;
46import android.util.ArrayMap;
Ned Burnsf098dbf2019-09-13 19:17:53 -040047
48import com.android.internal.statusbar.IStatusBarService;
Beverlyb6f4dc22020-01-10 14:58:20 -050049import com.android.systemui.DumpController;
50import com.android.systemui.Dumpable;
Beverlybac7f002020-01-24 15:30:30 -050051import com.android.systemui.statusbar.FeatureFlags;
Ned Burns012048d2020-01-08 19:57:30 -050052import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
53import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
54import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
55import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
56import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
57import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
Ned Burns7d2e9c12020-01-21 17:47:16 -050058import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
Ned Burns012048d2020-01-08 19:57:30 -050059import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
Ned Burnsf098dbf2019-09-13 19:17:53 -040060import com.android.systemui.util.Assert;
61
Beverlyb6f4dc22020-01-10 14:58:20 -050062import java.io.FileDescriptor;
63import java.io.PrintWriter;
Ned Burnsf098dbf2019-09-13 19:17:53 -040064import java.lang.annotation.Retention;
65import java.lang.annotation.RetentionPolicy;
66import java.util.ArrayList;
67import java.util.Collection;
68import java.util.Collections;
69import java.util.List;
70import java.util.Map;
Daulet Zhanguzind0549ae2020-01-03 11:08:54 +000071import java.util.Objects;
Ned Burnsf098dbf2019-09-13 19:17:53 -040072
73import javax.inject.Inject;
74import javax.inject.Singleton;
75
76/**
77 * Keeps a record of all of the "active" notifications, i.e. the notifications that are currently
78 * posted to the phone. This collection is unsorted, ungrouped, and unfiltered. Just because a
79 * notification appears in this collection doesn't mean that it's currently present in the shade
80 * (notifications can be hidden for a variety of reasons). Code that cares about what notifications
81 * are *visible* right now should register listeners later in the pipeline.
82 *
83 * Each notification is represented by a {@link NotificationEntry}, which is itself made up of two
84 * parts: a {@link StatusBarNotification} and a {@link Ranking}. When notifications are updated,
85 * their underlying SBNs and Rankings are swapped out, but the enclosing NotificationEntry (and its
86 * associated key) remain the same. In general, an SBN can only be updated when the notification is
87 * reposted by the source app; Rankings are updated much more often, usually every time there is an
88 * update from any kind from NotificationManager.
89 *
90 * In general, this collection closely mirrors the list maintained by NotificationManager, but it
91 * can occasionally diverge due to lifetime extenders (see
92 * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}).
93 *
94 * Interested parties can register listeners
95 * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are
96 * added, updated, or removed.
97 */
98@MainThread
99@Singleton
Beverlyb6f4dc22020-01-10 14:58:20 -0500100public class NotifCollection implements Dumpable {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400101 private final IStatusBarService mStatusBarService;
Beverlybac7f002020-01-24 15:30:30 -0500102 private final FeatureFlags mFeatureFlags;
Ned Burns7d2e9c12020-01-21 17:47:16 -0500103 private final NotifCollectionLogger mLogger;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400104
105 private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
106 private final Collection<NotificationEntry> mReadOnlyNotificationSet =
107 Collections.unmodifiableCollection(mNotificationSet.values());
108
Ned Burns77050aa2019-10-17 21:55:24 -0400109 @Nullable private CollectionReadyForBuildListener mBuildListener;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400110 private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
111 private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
112
113 private boolean mAttached = false;
114 private boolean mAmDispatchingToOtherCode;
115
116 @Inject
Beverlybac7f002020-01-24 15:30:30 -0500117 public NotifCollection(
118 IStatusBarService statusBarService,
119 DumpController dumpController,
Ned Burns7d2e9c12020-01-21 17:47:16 -0500120 FeatureFlags featureFlags,
121 NotifCollectionLogger logger) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400122 Assert.isMainThread();
123 mStatusBarService = statusBarService;
Ned Burns7d2e9c12020-01-21 17:47:16 -0500124 mLogger = logger;
Beverlyb6f4dc22020-01-10 14:58:20 -0500125 dumpController.registerDumpable(TAG, this);
Beverlybac7f002020-01-24 15:30:30 -0500126 mFeatureFlags = featureFlags;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400127 }
128
129 /** Initializes the NotifCollection and registers it to receive notification events. */
Ned Burnsa944ea32019-12-19 17:04:19 -0500130 public void attach(GroupCoalescer groupCoalescer) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400131 Assert.isMainThread();
132 if (mAttached) {
133 throw new RuntimeException("attach() called twice");
134 }
135 mAttached = true;
136
Ned Burnsa944ea32019-12-19 17:04:19 -0500137 groupCoalescer.setNotificationHandler(mNotifHandler);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400138 }
139
140 /**
141 * Sets the class responsible for converting the collection into the list of currently-visible
142 * notifications.
143 */
Ned Burns8172d3a2020-01-10 00:24:17 -0500144 void setBuildListener(CollectionReadyForBuildListener buildListener) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400145 Assert.isMainThread();
Ned Burns77050aa2019-10-17 21:55:24 -0400146 mBuildListener = buildListener;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400147 }
148
Ned Burns8172d3a2020-01-10 00:24:17 -0500149 /** @see NotifPipeline#getActiveNotifs() */
150 Collection<NotificationEntry> getActiveNotifs() {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400151 Assert.isMainThread();
152 return mReadOnlyNotificationSet;
153 }
154
Ned Burns8172d3a2020-01-10 00:24:17 -0500155 /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
156 void addCollectionListener(NotifCollectionListener listener) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400157 Assert.isMainThread();
158 mNotifCollectionListeners.add(listener);
159 }
160
Ned Burns8172d3a2020-01-10 00:24:17 -0500161 /** @see NotifPipeline#addNotificationLifetimeExtender(NotifLifetimeExtender) */
162 void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400163 Assert.isMainThread();
164 checkForReentrantCall();
165 if (mLifetimeExtenders.contains(extender)) {
166 throw new IllegalArgumentException("Extender " + extender + " already added.");
167 }
168 mLifetimeExtenders.add(extender);
169 extender.setCallback(this::onEndLifetimeExtension);
170 }
171
172 /**
173 * Dismiss a notification on behalf of the user.
174 */
Ned Burns8172d3a2020-01-10 00:24:17 -0500175 void dismissNotification(
Ned Burnsf098dbf2019-09-13 19:17:53 -0400176 NotificationEntry entry,
177 @CancellationReason int reason,
178 @NonNull DismissedByUserStats stats) {
179 Assert.isMainThread();
Daulet Zhanguzind0549ae2020-01-03 11:08:54 +0000180 Objects.requireNonNull(stats);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400181 checkForReentrantCall();
182
Evan Laird9afe7662019-10-16 17:16:39 -0400183 removeNotification(entry.getKey(), null, reason, stats);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400184 }
185
186 private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
187 Assert.isMainThread();
188
Ned Burnsa944ea32019-12-19 17:04:19 -0500189 postNotification(sbn, requireRanking(rankingMap, sbn.getKey()), rankingMap);
190 rebuildList();
191 }
192
193 private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
194 Assert.isMainThread();
195
Ned Burns7d2e9c12020-01-21 17:47:16 -0500196 mLogger.logNotifGroupPosted(batch.get(0).getSbn().getGroupKey(), batch.size());
197
Ned Burnsa944ea32019-12-19 17:04:19 -0500198 for (CoalescedEvent event : batch) {
199 postNotification(event.getSbn(), event.getRanking(), null);
200 }
201 rebuildList();
202 }
203
204 private void onNotificationRemoved(
205 StatusBarNotification sbn,
206 RankingMap rankingMap,
207 int reason) {
208 Assert.isMainThread();
209
Ned Burns7d2e9c12020-01-21 17:47:16 -0500210 mLogger.logNotifRemoved(sbn.getKey(), reason);
Ned Burnsa944ea32019-12-19 17:04:19 -0500211 removeNotification(sbn.getKey(), rankingMap, reason, null);
212 }
213
214 private void onNotificationRankingUpdate(RankingMap rankingMap) {
215 Assert.isMainThread();
216 applyRanking(rankingMap);
Beverlyed8aea22020-01-22 16:52:47 -0500217 dispatchNotificationRankingUpdate(rankingMap);
Ned Burnsa944ea32019-12-19 17:04:19 -0500218 rebuildList();
219 }
220
221 private void postNotification(
222 StatusBarNotification sbn,
223 Ranking ranking,
224 @Nullable RankingMap rankingMap) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400225 NotificationEntry entry = mNotificationSet.get(sbn.getKey());
226
227 if (entry == null) {
228 // A new notification!
Ned Burns7d2e9c12020-01-21 17:47:16 -0500229 mLogger.logNotifPosted(sbn.getKey());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400230
Ned Burnsa944ea32019-12-19 17:04:19 -0500231 entry = new NotificationEntry(sbn, ranking);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400232 mNotificationSet.put(sbn.getKey(), entry);
Ned Burnsa944ea32019-12-19 17:04:19 -0500233 if (rankingMap != null) {
234 applyRanking(rankingMap);
235 }
Ned Burnsf098dbf2019-09-13 19:17:53 -0400236
237 dispatchOnEntryAdded(entry);
238
239 } else {
240 // Update to an existing entry
Ned Burns7d2e9c12020-01-21 17:47:16 -0500241 mLogger.logNotifUpdated(sbn.getKey());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400242
243 // Notification is updated so it is essentially re-added and thus alive again. Don't
244 // need to keep its lifetime extended.
245 cancelLifetimeExtension(entry);
246
Ned Burns00b4b2d2019-10-17 22:09:27 -0400247 entry.setSbn(sbn);
Ned Burnsa944ea32019-12-19 17:04:19 -0500248 if (rankingMap != null) {
249 applyRanking(rankingMap);
250 }
Ned Burnsf098dbf2019-09-13 19:17:53 -0400251
252 dispatchOnEntryUpdated(entry);
253 }
Ned Burnsf098dbf2019-09-13 19:17:53 -0400254 }
255
256 private void removeNotification(
257 String key,
258 @Nullable RankingMap rankingMap,
259 @CancellationReason int reason,
Ned Burnsa944ea32019-12-19 17:04:19 -0500260 @Nullable DismissedByUserStats dismissedByUserStats) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400261
262 NotificationEntry entry = mNotificationSet.get(key);
263 if (entry == null) {
264 throw new IllegalStateException("No notification to remove with key " + key);
265 }
266
267 entry.mLifetimeExtenders.clear();
268 mAmDispatchingToOtherCode = true;
269 for (NotifLifetimeExtender extender : mLifetimeExtenders) {
270 if (extender.shouldExtendLifetime(entry, reason)) {
271 entry.mLifetimeExtenders.add(extender);
272 }
273 }
274 mAmDispatchingToOtherCode = false;
275
276 if (!isLifetimeExtended(entry)) {
Evan Laird9afe7662019-10-16 17:16:39 -0400277 mNotificationSet.remove(entry.getKey());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400278
279 if (dismissedByUserStats != null) {
280 try {
281 mStatusBarService.onNotificationClear(
Evan Laird9afe7662019-10-16 17:16:39 -0400282 entry.getSbn().getPackageName(),
283 entry.getSbn().getTag(),
284 entry.getSbn().getId(),
285 entry.getSbn().getUser().getIdentifier(),
286 entry.getSbn().getKey(),
Ned Burnsf098dbf2019-09-13 19:17:53 -0400287 dismissedByUserStats.dismissalSurface,
288 dismissedByUserStats.dismissalSentiment,
289 dismissedByUserStats.notificationVisibility);
290 } catch (RemoteException e) {
291 // system process is dead if we're here.
292 }
293 }
294
295 if (rankingMap != null) {
296 applyRanking(rankingMap);
297 }
298
299 dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */);
300 }
301
302 rebuildList();
303 }
304
Ned Burnsa944ea32019-12-19 17:04:19 -0500305 private void applyRanking(@NonNull RankingMap rankingMap) {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400306 for (NotificationEntry entry : mNotificationSet.values()) {
307 if (!isLifetimeExtended(entry)) {
Evan Laird9afe7662019-10-16 17:16:39 -0400308 Ranking ranking = requireRanking(rankingMap, entry.getKey());
Ned Burnsf098dbf2019-09-13 19:17:53 -0400309 entry.setRanking(ranking);
Beverlyce58b4a2020-01-08 15:31:15 -0500310
311 // TODO: (b/145659174) update the sbn's overrideGroupKey in
312 // NotificationEntry.setRanking instead of here once we fully migrate to the
313 // NewNotifPipeline
Beverlybac7f002020-01-24 15:30:30 -0500314 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
315 final String newOverrideGroupKey = ranking.getOverrideGroupKey();
316 if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
317 newOverrideGroupKey)) {
318 entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
319 }
Beverlyce58b4a2020-01-08 15:31:15 -0500320 }
Ned Burnsf098dbf2019-09-13 19:17:53 -0400321 }
322 }
323 }
324
325 private void rebuildList() {
Ned Burns77050aa2019-10-17 21:55:24 -0400326 if (mBuildListener != null) {
327 mBuildListener.onBuildList(mReadOnlyNotificationSet);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400328 }
329 }
330
331 private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) {
332 Assert.isMainThread();
333 if (!mAttached) {
334 return;
335 }
336 checkForReentrantCall();
337
338 if (!entry.mLifetimeExtenders.remove(extender)) {
339 throw new IllegalStateException(
340 String.format(
341 "Cannot end lifetime extension for extender \"%s\" (%s)",
342 extender.getName(),
343 extender));
344 }
345
346 if (!isLifetimeExtended(entry)) {
347 // TODO: This doesn't need to be undefined -- we can set either EXTENDER_EXPIRED or
348 // save the original reason
Evan Laird9afe7662019-10-16 17:16:39 -0400349 removeNotification(entry.getKey(), null, REASON_UNKNOWN, null);
Ned Burnsf098dbf2019-09-13 19:17:53 -0400350 }
351 }
352
353 private void cancelLifetimeExtension(NotificationEntry entry) {
354 mAmDispatchingToOtherCode = true;
355 for (NotifLifetimeExtender extender : entry.mLifetimeExtenders) {
356 extender.cancelLifetimeExtension(entry);
357 }
358 mAmDispatchingToOtherCode = false;
359 entry.mLifetimeExtenders.clear();
360 }
361
362 private boolean isLifetimeExtended(NotificationEntry entry) {
363 return entry.mLifetimeExtenders.size() > 0;
364 }
365
366 private void checkForReentrantCall() {
367 if (mAmDispatchingToOtherCode) {
368 throw new IllegalStateException("Reentrant call detected");
369 }
370 }
371
372 private static Ranking requireRanking(RankingMap rankingMap, String key) {
373 // TODO: Modify RankingMap so that we don't have to make a copy here
374 Ranking ranking = new Ranking();
375 if (!rankingMap.getRanking(key, ranking)) {
376 throw new IllegalArgumentException("Ranking map doesn't contain key: " + key);
377 }
378 return ranking;
379 }
380
381 private void dispatchOnEntryAdded(NotificationEntry entry) {
382 mAmDispatchingToOtherCode = true;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400383 for (NotifCollectionListener listener : mNotifCollectionListeners) {
384 listener.onEntryAdded(entry);
385 }
386 mAmDispatchingToOtherCode = false;
387 }
388
389 private void dispatchOnEntryUpdated(NotificationEntry entry) {
390 mAmDispatchingToOtherCode = true;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400391 for (NotifCollectionListener listener : mNotifCollectionListeners) {
392 listener.onEntryUpdated(entry);
393 }
394 mAmDispatchingToOtherCode = false;
395 }
396
Beverlyed8aea22020-01-22 16:52:47 -0500397 private void dispatchNotificationRankingUpdate(RankingMap map) {
398 mAmDispatchingToOtherCode = true;
399 for (NotifCollectionListener listener : mNotifCollectionListeners) {
400 listener.onRankingUpdate(map);
401 }
402 mAmDispatchingToOtherCode = false;
403 }
404
Ned Burnsf098dbf2019-09-13 19:17:53 -0400405 private void dispatchOnEntryRemoved(
406 NotificationEntry entry,
407 @CancellationReason int reason,
408 boolean removedByUser) {
409 mAmDispatchingToOtherCode = true;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400410 for (NotifCollectionListener listener : mNotifCollectionListeners) {
411 listener.onEntryRemoved(entry, reason, removedByUser);
412 }
413 mAmDispatchingToOtherCode = false;
414 }
415
Ned Burnsa944ea32019-12-19 17:04:19 -0500416 private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() {
Ned Burnsf098dbf2019-09-13 19:17:53 -0400417 @Override
418 public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
419 NotifCollection.this.onNotificationPosted(sbn, rankingMap);
420 }
421
422 @Override
Ned Burnsa944ea32019-12-19 17:04:19 -0500423 public void onNotificationBatchPosted(List<CoalescedEvent> events) {
424 NotifCollection.this.onNotificationGroupPosted(events);
425 }
426
427 @Override
Ned Burnsf098dbf2019-09-13 19:17:53 -0400428 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
429 NotifCollection.this.onNotificationRemoved(sbn, rankingMap, REASON_UNKNOWN);
430 }
431
432 @Override
433 public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
434 int reason) {
435 NotifCollection.this.onNotificationRemoved(sbn, rankingMap, reason);
436 }
437
438 @Override
439 public void onNotificationRankingUpdate(RankingMap rankingMap) {
440 NotifCollection.this.onNotificationRankingUpdate(rankingMap);
441 }
442 };
443
444 private static final String TAG = "NotifCollection";
445
446 @IntDef(prefix = { "REASON_" }, value = {
447 REASON_UNKNOWN,
448 REASON_CLICK,
449 REASON_CANCEL_ALL,
450 REASON_ERROR,
451 REASON_PACKAGE_CHANGED,
452 REASON_USER_STOPPED,
453 REASON_PACKAGE_BANNED,
454 REASON_APP_CANCEL,
455 REASON_APP_CANCEL_ALL,
456 REASON_LISTENER_CANCEL,
457 REASON_LISTENER_CANCEL_ALL,
458 REASON_GROUP_SUMMARY_CANCELED,
459 REASON_GROUP_OPTIMIZATION,
460 REASON_PACKAGE_SUSPENDED,
461 REASON_PROFILE_TURNED_OFF,
462 REASON_UNAUTOBUNDLED,
463 REASON_CHANNEL_BANNED,
464 REASON_SNOOZED,
465 REASON_TIMEOUT,
466 })
467 @Retention(RetentionPolicy.SOURCE)
Ned Burns012048d2020-01-08 19:57:30 -0500468 public @interface CancellationReason {}
Ned Burnsf098dbf2019-09-13 19:17:53 -0400469
470 public static final int REASON_UNKNOWN = 0;
Beverlyb6f4dc22020-01-10 14:58:20 -0500471
472 @Override
473 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
474 final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
475
476 pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
477 if (entries.size() == 0) {
478 pw.println("\t\t None");
479 }
480 pw.println(
481 ListDumper.dumpList(
482 entries,
483 true,
484 "\t\t"));
485 }
Ned Burnsf098dbf2019-09-13 19:17:53 -0400486}