blob: 879a8dfa2875c96c1bf946288d6655ed8d34347f [file] [log] [blame]
Eliot Courtneya6d8cf22017-10-20 13:26:58 +09001/*
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 */
Rohan Shah20790b82018-07-02 17:21:04 -070016package com.android.systemui.statusbar.notification;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090017
Mady Mellorc2ff0112019-03-28 14:18:06 -070018import static android.service.notification.NotificationListenerService.REASON_CANCEL;
19import static android.service.notification.NotificationListenerService.REASON_ERROR;
20
Tony Mak628cb932018-06-19 18:30:41 +010021import android.annotation.Nullable;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090022import android.app.Notification;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090023import android.content.Context;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090024import android.service.notification.NotificationListenerService;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090025import android.service.notification.StatusBarNotification;
Tony Mak628cb932018-06-19 18:30:41 +010026import android.util.ArrayMap;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090027import android.util.Log;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090028
Julia Reynolds91590062018-04-02 16:24:11 -040029import com.android.internal.annotations.VisibleForTesting;
Dieter Hsud39f0d52018-04-14 02:08:30 +080030import com.android.internal.statusbar.NotificationVisibility;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090031import com.android.systemui.Dependency;
32import com.android.systemui.Dumpable;
Kevin38ce6fa2018-10-17 16:00:14 -070033import com.android.systemui.statusbar.NotificationLifetimeExtender;
Rohan Shah20790b82018-07-02 17:21:04 -070034import com.android.systemui.statusbar.NotificationPresenter;
35import com.android.systemui.statusbar.NotificationRemoteInputManager;
Mady Mellorc2ff0112019-03-28 14:18:06 -070036import com.android.systemui.statusbar.NotificationRemoveInterceptor;
Rohan Shah20790b82018-07-02 17:21:04 -070037import com.android.systemui.statusbar.NotificationUiAdjustment;
38import com.android.systemui.statusbar.NotificationUpdateHandler;
Ned Burnsf81c4c42019-01-07 14:10:43 -050039import com.android.systemui.statusbar.notification.collection.NotificationData;
40import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
41import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Ned Burnsc5864672019-02-20 12:57:29 -050042import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
Gustav Senntonf892fe92019-01-22 15:31:42 +000043import com.android.systemui.statusbar.notification.logging.NotificationLogger;
Ned Burns1a5e22f2019-02-14 15:11:52 -050044import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
45import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
Rohan Shah20790b82018-07-02 17:21:04 -070046import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090047import com.android.systemui.statusbar.policy.HeadsUpManager;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090048import com.android.systemui.util.leak.LeakDetector;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
52import java.util.ArrayList;
53import java.util.HashMap;
54import java.util.List;
Ned Burnsa5dad552019-01-29 17:59:10 -050055import java.util.Map;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090056
57/**
58 * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
59 * It also handles tasks such as their inflation and their interaction with other
60 * Notification.*Manager objects.
61 */
Gus Prevas8ba88a82018-12-18 11:13:44 -050062public class NotificationEntryManager implements
63 Dumpable,
Ned Burns1a5e22f2019-02-14 15:11:52 -050064 NotificationContentInflater.InflationCallback,
Gus Prevas8ba88a82018-12-18 11:13:44 -050065 NotificationUpdateHandler,
Ned Burns01e38212019-01-03 16:32:52 -050066 VisualStabilityManager.Callback {
Julia Reynoldsfc640012018-02-21 12:25:27 -050067 private static final String TAG = "NotificationEntryMgr";
Ned Burnsd35708c2019-01-09 15:38:03 -050068 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Eliot Courtney6c313d32017-12-14 19:57:51 +090069
Mady Mellor1a4e86f2019-05-03 16:07:23 -070070 /**
71 * Used when a notification is removed and it doesn't have a reason that maps to one of the
72 * reasons defined in NotificationListenerService
73 * (e.g. {@link NotificationListenerService.REASON_CANCEL})
74 */
75 public static final int UNDEFINED_DISMISS_REASON = 0;
76
Ned Burnsd35708c2019-01-09 15:38:03 -050077 @VisibleForTesting
Ned Burnsf81c4c42019-01-07 14:10:43 -050078 protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090079
Ned Burnsa5dad552019-01-29 17:59:10 -050080 private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
81 new ArrayMap<>();
82
Jason Monk297c04e2018-08-23 17:16:59 -040083 // Lazily retrieved dependencies
84 private NotificationRemoteInputManager mRemoteInputManager;
Gus Prevas8ba88a82018-12-18 11:13:44 -050085 private NotificationRowBinder mNotificationRowBinder;
Eliot Courtney6c313d32017-12-14 19:57:51 +090086
Jason Monk297c04e2018-08-23 17:16:59 -040087 private NotificationPresenter mPresenter;
Ned Burnsdc10c952018-11-21 14:36:21 -050088 private NotificationListenerService.RankingMap mLatestRankingMap;
Ned Burnsd35708c2019-01-09 15:38:03 -050089 @VisibleForTesting
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090090 protected NotificationData mNotificationData;
Ned Burnsf36c6252019-01-09 19:12:33 -050091
Ned Burnsdc10c952018-11-21 14:36:21 -050092 @VisibleForTesting
93 final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
Kevina5ff1fa2018-08-21 16:35:48 -070094 = new ArrayList<>();
Gus Prevasd65c2db2018-12-18 17:13:38 -050095 private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
Mady Mellorc2ff0112019-03-28 14:18:06 -070096 private NotificationRemoveInterceptor mRemoveInterceptor;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090097
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090098 @Override
99 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
100 pw.println("NotificationEntryManager state:");
101 pw.print(" mPendingNotifications=");
102 if (mPendingNotifications.size() == 0) {
103 pw.println("null");
104 } else {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500105 for (NotificationEntry entry : mPendingNotifications.values()) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900106 pw.println(entry.notification);
107 }
108 }
Ned Burnsa5dad552019-01-29 17:59:10 -0500109 pw.println(" Lifetime-extended notifications:");
110 if (mRetainedNotifications.isEmpty()) {
111 pw.println(" None");
112 } else {
113 for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry
114 : mRetainedNotifications.entrySet()) {
115 pw.println(" " + entry.getKey().notification + " retained by "
116 + entry.getValue().getClass().getName());
117 }
118 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900119 }
120
Eliot Courtney6c313d32017-12-14 19:57:51 +0900121 public NotificationEntryManager(Context context) {
Jason Monk297c04e2018-08-23 17:16:59 -0400122 mNotificationData = new NotificationData();
Jason Monk297c04e2018-08-23 17:16:59 -0400123 }
124
Gus Prevasd65c2db2018-12-18 17:13:38 -0500125 /** Adds a {@link NotificationEntryListener}. */
126 public void addNotificationEntryListener(NotificationEntryListener listener) {
127 mNotificationEntryListeners.add(listener);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900128 }
129
Mady Mellorc2ff0112019-03-28 14:18:06 -0700130 /** Sets the {@link NotificationRemoveInterceptor}. */
131 public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
132 mRemoveInterceptor = interceptor;
133 }
134
Jason Monk297c04e2018-08-23 17:16:59 -0400135 /**
136 * Our dependencies can have cyclic references, so some need to be lazy
137 */
138 private NotificationRemoteInputManager getRemoteInputManager() {
139 if (mRemoteInputManager == null) {
140 mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
141 }
142 return mRemoteInputManager;
143 }
144
Ned Burnsc5864672019-02-20 12:57:29 -0500145 public void setRowBinder(NotificationRowBinder notificationRowBinder) {
Ned Burnsef2ef6c2019-01-02 16:48:08 -0500146 mNotificationRowBinder = notificationRowBinder;
147 }
148
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900149 public void setUpWithPresenter(NotificationPresenter presenter,
Gus Prevas8621bd22018-12-20 15:04:25 -0500150 NotificationListContainer listContainer,
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900151 HeadsUpManager headsUpManager) {
152 mPresenter = presenter;
Gus Prevas92586462019-01-04 16:06:12 -0500153 mNotificationData.setHeadsUpManager(headsUpManager);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900154 }
155
Gus Prevas5c84abb2018-12-28 16:41:49 -0500156 /** Adds multiple {@link NotificationLifetimeExtender}s. */
157 public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
158 for (NotificationLifetimeExtender extender : extenders) {
159 addNotificationLifetimeExtender(extender);
160 }
161 }
162
163 /** Adds a {@link NotificationLifetimeExtender}. */
164 public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
165 mNotificationLifetimeExtenders.add(extender);
Mady Mellor1a4e86f2019-05-03 16:07:23 -0700166 extender.setCallback(key -> removeNotification(key, mLatestRankingMap,
167 UNDEFINED_DISMISS_REASON));
Gus Prevas5c84abb2018-12-28 16:41:49 -0500168 }
169
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900170 public NotificationData getNotificationData() {
171 return mNotificationData;
172 }
173
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900174 @Override
175 public void onReorderingAllowed() {
176 updateNotifications();
177 }
178
Mady Mellorc2ff0112019-03-28 14:18:06 -0700179 /**
180 * Requests a notification to be removed.
181 *
182 * @param n the notification to remove.
183 * @param reason why it is being removed e.g. {@link NotificationListenerService#REASON_CANCEL},
184 * or 0 if unknown.
185 */
186 public void performRemoveNotification(StatusBarNotification n, int reason) {
Selim Cinek7bc9be72019-03-06 18:42:05 -0800187 final NotificationVisibility nv = obtainVisibility(n.getKey());
Gus Prevasca1b6f72018-12-28 10:53:11 -0500188 removeNotificationInternal(
Mady Mellorc2ff0112019-03-28 14:18:06 -0700189 n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */,
190 reason);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900191 }
192
Selim Cinek7bc9be72019-03-06 18:42:05 -0800193 private NotificationVisibility obtainVisibility(String key) {
194 final int rank = mNotificationData.getRank(key);
195 final int count = mNotificationData.getActiveNotifications().size();
196 NotificationVisibility.NotificationLocation location =
197 NotificationLogger.getNotificationLocation(getNotificationData().get(key));
198 return NotificationVisibility.obtain(key, rank, count, true, location);
199 }
200
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900201 private void abortExistingInflation(String key) {
202 if (mPendingNotifications.containsKey(key)) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500203 NotificationEntry entry = mPendingNotifications.get(key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900204 entry.abortTask();
205 mPendingNotifications.remove(key);
206 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500207 NotificationEntry addedEntry = mNotificationData.get(key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900208 if (addedEntry != null) {
209 addedEntry.abortTask();
210 }
211 }
212
Ned Burnsdc10c952018-11-21 14:36:21 -0500213 /**
214 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
215 * about the failure.
216 *
217 * WARNING: this will call back into us. Don't hold any locks.
218 */
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900219 @Override
Ned Burnsdc10c952018-11-21 14:36:21 -0500220 public void handleInflationException(StatusBarNotification n, Exception e) {
Gus Prevasdca2be52018-12-21 11:25:10 -0500221 removeNotificationInternal(
Mady Mellorc2ff0112019-03-28 14:18:06 -0700222 n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */,
223 REASON_ERROR);
Gus Prevasca1b6f72018-12-28 10:53:11 -0500224 for (NotificationEntryListener listener : mNotificationEntryListeners) {
225 listener.onInflationError(n, e);
Ned Burnsdc10c952018-11-21 14:36:21 -0500226 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900227 }
228
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900229 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500230 public void onAsyncInflationFinished(NotificationEntry entry,
Kevind4660b22018-09-27 10:57:35 -0700231 @InflationFlag int inflatedFlags) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900232 mPendingNotifications.remove(entry.key);
233 // If there was an async task started after the removal, we don't want to add it back to
234 // the list, otherwise we might get leaks.
Evan Laird94492852018-10-25 13:43:01 -0400235 if (!entry.isRowRemoved()) {
Kevin01a53cb2018-11-09 18:19:54 -0800236 boolean isNew = mNotificationData.get(entry.key) == null;
237 if (isNew) {
Gus Prevasb43dc652018-12-20 13:11:45 -0500238 for (NotificationEntryListener listener : mNotificationEntryListeners) {
239 listener.onEntryInflated(entry, inflatedFlags);
240 }
Ned Burns69760872019-01-03 15:36:38 -0500241 mNotificationData.add(entry);
Ned Burnsf36c6252019-01-09 19:12:33 -0500242 for (NotificationEntryListener listener : mNotificationEntryListeners) {
243 listener.onBeforeNotificationAdded(entry);
244 }
Ned Burns69760872019-01-03 15:36:38 -0500245 updateNotifications();
246 for (NotificationEntryListener listener : mNotificationEntryListeners) {
247 listener.onNotificationAdded(entry);
248 }
Kevin01a53cb2018-11-09 18:19:54 -0800249 } else {
Gus Prevasd65c2db2018-12-18 17:13:38 -0500250 for (NotificationEntryListener listener : mNotificationEntryListeners) {
251 listener.onEntryReinflated(entry);
Ned Burns3d6b3962018-12-07 21:26:00 -0500252 }
Kevin01a53cb2018-11-09 18:19:54 -0800253 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900254 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900255 }
256
257 @Override
Mady Mellorc2ff0112019-03-28 14:18:06 -0700258 public void removeNotification(String key, NotificationListenerService.RankingMap ranking,
259 int reason) {
Selim Cinek7bc9be72019-03-06 18:42:05 -0800260 removeNotificationInternal(key, ranking, obtainVisibility(key), false /* forceRemove */,
Mady Mellorc2ff0112019-03-28 14:18:06 -0700261 false /* removedByUser */, reason);
Kevina5ff1fa2018-08-21 16:35:48 -0700262 }
263
Gus Prevasdca2be52018-12-21 11:25:10 -0500264 private void removeNotificationInternal(
265 String key,
266 @Nullable NotificationListenerService.RankingMap ranking,
Gus Prevasca1b6f72018-12-28 10:53:11 -0500267 @Nullable NotificationVisibility visibility,
Gus Prevasdca2be52018-12-21 11:25:10 -0500268 boolean forceRemove,
Mady Mellorc2ff0112019-03-28 14:18:06 -0700269 boolean removedByUser,
270 int reason) {
271
272 if (mRemoveInterceptor != null
273 && mRemoveInterceptor.onNotificationRemoveRequested(key, reason)) {
274 // Remove intercepted; skip
275 return;
276 }
277
Ned Burnsf81c4c42019-01-07 14:10:43 -0500278 final NotificationEntry entry = mNotificationData.get(key);
Ned Burns3d6b3962018-12-07 21:26:00 -0500279
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900280 abortExistingInflation(key);
Gus Prevasf37435a2018-12-20 15:40:01 -0500281
Gus Prevasf37435a2018-12-20 15:40:01 -0500282 boolean lifetimeExtended = false;
283
Gus Prevasd65c2db2018-12-18 17:13:38 -0500284 if (entry != null) {
Gus Prevasf37435a2018-12-20 15:40:01 -0500285 // If a manager needs to keep the notification around for whatever reason, we
286 // keep the notification
Selim Cinek7bc9be72019-03-06 18:42:05 -0800287 boolean entryDismissed = entry.isRowDismissed();
288 if (!forceRemove && !entryDismissed) {
Gus Prevasf37435a2018-12-20 15:40:01 -0500289 for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
290 if (extender.shouldExtendLifetime(entry)) {
291 mLatestRankingMap = ranking;
Ned Burnsa5dad552019-01-29 17:59:10 -0500292 extendLifetime(entry, extender);
Gus Prevasf37435a2018-12-20 15:40:01 -0500293 lifetimeExtended = true;
294 break;
295 }
Kevina5ff1fa2018-08-21 16:35:48 -0700296 }
297 }
Gus Prevasf37435a2018-12-20 15:40:01 -0500298
299 if (!lifetimeExtended) {
300 // At this point, we are guaranteed the notification will be removed
301
302 // Ensure any managers keeping the lifetime extended stop managing the entry
Ned Burnsa5dad552019-01-29 17:59:10 -0500303 cancelLifetimeExtension(entry);
Gus Prevasf37435a2018-12-20 15:40:01 -0500304
Gus Prevasf37435a2018-12-20 15:40:01 -0500305 if (entry.rowExists()) {
306 entry.removeRow();
Gus Prevasf37435a2018-12-20 15:40:01 -0500307 }
308
309 // Let's remove the children if this was a summary
310 handleGroupSummaryRemoved(key);
311
Ned Burns69760872019-01-03 15:36:38 -0500312 mNotificationData.remove(key, ranking);
313 updateNotifications();
314 Dependency.get(LeakDetector.class).trackGarbage(entry);
Selim Cinek7bc9be72019-03-06 18:42:05 -0800315 removedByUser |= entryDismissed;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900316
Ned Burnsef2ef6c2019-01-02 16:48:08 -0500317 for (NotificationEntryListener listener : mNotificationEntryListeners) {
318 listener.onEntryRemoved(entry, visibility, removedByUser);
319 }
320 }
Gus Prevas8621bd22018-12-20 15:04:25 -0500321 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900322 }
323
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900324 /**
325 * Ensures that the group children are cancelled immediately when the group summary is cancelled
326 * instead of waiting for the notification manager to send all cancels. Otherwise this could
327 * lead to flickers.
328 *
329 * This also ensures that the animation looks nice and only consists of a single disappear
330 * animation instead of multiple.
331 * @param key the key of the notification was removed
332 *
333 */
334 private void handleGroupSummaryRemoved(String key) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500335 NotificationEntry entry = mNotificationData.get(key);
Evan Laird94492852018-10-25 13:43:01 -0400336 if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
337 if (entry.notification.getOverrideGroupKey() != null && !entry.isRowDismissed()) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900338 // We don't want to remove children for autobundled notifications as they are not
339 // always cancelled. We only remove them if they were dismissed by the user.
340 return;
341 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500342 List<NotificationEntry> childEntries = entry.getChildren();
Evan Laird94492852018-10-25 13:43:01 -0400343 if (childEntries == null) {
344 return;
345 }
346 for (int i = 0; i < childEntries.size(); i++) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500347 NotificationEntry childEntry = childEntries.get(i);
Evan Laird94492852018-10-25 13:43:01 -0400348 boolean isForeground = (entry.notification.getNotification().flags
Selim Cinek038e2592018-08-16 16:30:45 -0700349 & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Kevina5ff1fa2018-08-21 16:35:48 -0700350 boolean keepForReply =
Jason Monk297c04e2018-08-23 17:16:59 -0400351 getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
352 || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
Selim Cinek038e2592018-08-16 16:30:45 -0700353 if (isForeground || keepForReply) {
354 // the child is a foreground service notification which we can't remove or it's
355 // a child we're keeping around for reply!
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900356 continue;
357 }
Evan Lairdce2d1af2019-05-30 16:00:22 -0400358 childEntry.setKeepInParent(true);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900359 // we need to set this state earlier as otherwise we might generate some weird
360 // animations
Evan Lairdce2d1af2019-05-30 16:00:22 -0400361 childEntry.removeRow();
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900362 }
363 }
364 }
365
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900366 private void addNotificationInternal(StatusBarNotification notification,
Tony Mak628cb932018-06-19 18:30:41 +0100367 NotificationListenerService.RankingMap rankingMap) throws InflationException {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900368 String key = notification.getKey();
Kevina97ea052018-09-11 13:53:18 -0700369 if (DEBUG) {
370 Log.d(TAG, "addNotification key=" + key);
371 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900372
Tony Mak628cb932018-06-19 18:30:41 +0100373 mNotificationData.updateRanking(rankingMap);
374 NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
375 rankingMap.getRanking(key, ranking);
Ned Burns69760872019-01-03 15:36:38 -0500376
Ned Burnsf81c4c42019-01-07 14:10:43 -0500377 NotificationEntry entry = new NotificationEntry(notification, ranking);
Ned Burns69760872019-01-03 15:36:38 -0500378
379 Dependency.get(LeakDetector.class).trackInstance(entry);
380 // Construct the expanded view.
Mady Mellorc2ff0112019-03-28 14:18:06 -0700381 requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
382 REASON_CANCEL));
Ned Burns69760872019-01-03 15:36:38 -0500383
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900384 abortExistingInflation(key);
385
Gus Prevasd65c2db2018-12-18 17:13:38 -0500386 mPendingNotifications.put(key, entry);
387 for (NotificationEntryListener listener : mNotificationEntryListeners) {
388 listener.onPendingEntryAdded(entry);
Ned Burns3d6b3962018-12-07 21:26:00 -0500389 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900390 }
391
392 @Override
393 public void addNotification(StatusBarNotification notification,
394 NotificationListenerService.RankingMap ranking) {
395 try {
396 addNotificationInternal(notification, ranking);
397 } catch (InflationException e) {
398 handleInflationException(notification, e);
399 }
400 }
401
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900402 private void updateNotificationInternal(StatusBarNotification notification,
403 NotificationListenerService.RankingMap ranking) throws InflationException {
404 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
405
406 final String key = notification.getKey();
407 abortExistingInflation(key);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500408 NotificationEntry entry = mNotificationData.get(key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900409 if (entry == null) {
410 return;
411 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900412
Kevina5ff1fa2018-08-21 16:35:48 -0700413 // Notification is updated so it is essentially re-added and thus alive again. Don't need
Kevine9e938c2018-09-06 13:38:11 -0700414 // to keep its lifetime extended.
Ned Burnsa5dad552019-01-29 17:59:10 -0500415 cancelLifetimeExtension(entry);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900416
Gus Prevas26d91272018-12-21 16:43:28 -0500417 mNotificationData.update(entry, ranking, notification);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900418
Mady Mellor0ad5b9d2019-01-08 14:59:55 -0800419 for (NotificationEntryListener listener : mNotificationEntryListeners) {
420 listener.onPreEntryUpdated(entry);
421 }
422
Mady Mellorc2ff0112019-03-28 14:18:06 -0700423 requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
424 REASON_CANCEL));
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900425 updateNotifications();
426
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900427 if (DEBUG) {
428 // Is this for you?
Jason Monk297c04e2018-08-23 17:16:59 -0400429 boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
430 .isNotificationForCurrentProfiles(notification);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900431 Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
432 }
433
Gus Prevasb43dc652018-12-20 13:11:45 -0500434 for (NotificationEntryListener listener : mNotificationEntryListeners) {
Mady Mellor0ad5b9d2019-01-08 14:59:55 -0800435 listener.onPostEntryUpdated(entry);
Gus Prevasb43dc652018-12-20 13:11:45 -0500436 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900437 }
438
439 @Override
440 public void updateNotification(StatusBarNotification notification,
441 NotificationListenerService.RankingMap ranking) {
442 try {
443 updateNotificationInternal(notification, ranking);
444 } catch (InflationException e) {
445 handleInflationException(notification, e);
446 }
447 }
448
449 public void updateNotifications() {
450 mNotificationData.filterAndSort();
Ned Burnsf36c6252019-01-09 19:12:33 -0500451 if (mPresenter != null) {
452 mPresenter.updateNotificationViews();
453 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900454 }
455
Ned Burns6b19b082019-02-12 18:50:47 -0500456 @Override
Tony Mak628cb932018-06-19 18:30:41 +0100457 public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500458 List<NotificationEntry> entries = new ArrayList<>();
Tony Mak628cb932018-06-19 18:30:41 +0100459 entries.addAll(mNotificationData.getActiveNotifications());
460 entries.addAll(mPendingNotifications.values());
461
462 // Has a copy of the current UI adjustments.
463 ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
Gus Prevas33fbc152018-12-04 13:17:32 -0500464 ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500465 for (NotificationEntry entry : entries) {
Tony Mak628cb932018-06-19 18:30:41 +0100466 NotificationUiAdjustment adjustment =
467 NotificationUiAdjustment.extractFromNotificationEntry(entry);
468 oldAdjustments.put(entry.key, adjustment);
Gus Prevas33fbc152018-12-04 13:17:32 -0500469 oldImportances.put(entry.key, entry.importance);
Tony Mak628cb932018-06-19 18:30:41 +0100470 }
471
472 // Populate notification entries from the new rankings.
473 mNotificationData.updateRanking(rankingMap);
474 updateRankingOfPendingNotifications(rankingMap);
475
476 // By comparing the old and new UI adjustments, reinflate the view accordingly.
Ned Burnsf81c4c42019-01-07 14:10:43 -0500477 for (NotificationEntry entry : entries) {
Ned Burnsc5864672019-02-20 12:57:29 -0500478 requireBinder().onNotificationRankingUpdated(
Gus Prevas8ba88a82018-12-18 11:13:44 -0500479 entry,
480 oldImportances.get(entry.key),
481 oldAdjustments.get(entry.key),
Ned Burnsf529ec22019-02-12 19:33:50 -0500482 NotificationUiAdjustment.extractFromNotificationEntry(entry));
Tony Mak628cb932018-06-19 18:30:41 +0100483 }
484
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900485 updateNotifications();
Mark Renoufbbcf07f2019-05-09 10:42:43 -0400486
487 for (NotificationEntryListener listener : mNotificationEntryListeners) {
488 listener.onNotificationRankingUpdated(rankingMap);
489 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900490 }
491
Tony Mak628cb932018-06-19 18:30:41 +0100492 private void updateRankingOfPendingNotifications(
493 @Nullable NotificationListenerService.RankingMap rankingMap) {
494 if (rankingMap == null) {
495 return;
496 }
497 NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500498 for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
Tony Mak628cb932018-06-19 18:30:41 +0100499 rankingMap.getRanking(pendingNotification.key, tmpRanking);
500 pendingNotification.populateFromRanking(tmpRanking);
501 }
502 }
503
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900504 /**
Ned Burns3d6b3962018-12-07 21:26:00 -0500505 * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
506 * notifications whose views have not yet been inflated. In general, the system pretends like
507 * these don't exist, although there are a couple exceptions.
508 */
Ned Burnsf81c4c42019-01-07 14:10:43 -0500509 public Iterable<NotificationEntry> getPendingNotificationsIterator() {
Ned Burns3d6b3962018-12-07 21:26:00 -0500510 return mPendingNotifications.values();
511 }
Ned Burnsa5dad552019-01-29 17:59:10 -0500512
513 private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) {
514 NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
515 if (activeExtender != null && activeExtender != extender) {
516 activeExtender.setShouldManageLifetime(entry, false);
517 }
518 mRetainedNotifications.put(entry, extender);
519 extender.setShouldManageLifetime(entry, true);
520 }
521
522 private void cancelLifetimeExtension(NotificationEntry entry) {
523 NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
524 if (activeExtender != null) {
525 activeExtender.setShouldManageLifetime(entry, false);
526 }
527 }
Ned Burnsc5864672019-02-20 12:57:29 -0500528
529 private NotificationRowBinder requireBinder() {
530 if (mNotificationRowBinder == null) {
531 throw new RuntimeException("You must initialize NotificationEntryManager by calling"
532 + "setRowBinder() before using.");
533 }
534 return mNotificationRowBinder;
535 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900536}