blob: f8fef7d4778c8d38f0b90c014450feeccbe7161b [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
Govinda Wasserman3e7ce552019-08-13 11:35:44 -040057import javax.inject.Inject;
58import javax.inject.Singleton;
59
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090060/**
61 * NotificationEntryManager is responsible for the adding, removing, and updating of notifications.
62 * It also handles tasks such as their inflation and their interaction with other
63 * Notification.*Manager objects.
64 */
Govinda Wasserman3e7ce552019-08-13 11:35:44 -040065@Singleton
Gus Prevas8ba88a82018-12-18 11:13:44 -050066public class NotificationEntryManager implements
67 Dumpable,
Ned Burns1a5e22f2019-02-14 15:11:52 -050068 NotificationContentInflater.InflationCallback,
Gus Prevas8ba88a82018-12-18 11:13:44 -050069 NotificationUpdateHandler,
Ned Burns01e38212019-01-03 16:32:52 -050070 VisualStabilityManager.Callback {
Julia Reynoldsfc640012018-02-21 12:25:27 -050071 private static final String TAG = "NotificationEntryMgr";
Ned Burnsd35708c2019-01-09 15:38:03 -050072 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Eliot Courtney6c313d32017-12-14 19:57:51 +090073
Mady Mellor1a4e86f2019-05-03 16:07:23 -070074 /**
75 * Used when a notification is removed and it doesn't have a reason that maps to one of the
76 * reasons defined in NotificationListenerService
77 * (e.g. {@link NotificationListenerService.REASON_CANCEL})
78 */
79 public static final int UNDEFINED_DISMISS_REASON = 0;
80
Ned Burnsd35708c2019-01-09 15:38:03 -050081 @VisibleForTesting
Ned Burnsf81c4c42019-01-07 14:10:43 -050082 protected final HashMap<String, NotificationEntry> mPendingNotifications = new HashMap<>();
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090083
Ned Burnsa5dad552019-01-29 17:59:10 -050084 private final Map<NotificationEntry, NotificationLifetimeExtender> mRetainedNotifications =
85 new ArrayMap<>();
86
Jason Monk297c04e2018-08-23 17:16:59 -040087 // Lazily retrieved dependencies
88 private NotificationRemoteInputManager mRemoteInputManager;
Gus Prevas8ba88a82018-12-18 11:13:44 -050089 private NotificationRowBinder mNotificationRowBinder;
Eliot Courtney6c313d32017-12-14 19:57:51 +090090
Jason Monk297c04e2018-08-23 17:16:59 -040091 private NotificationPresenter mPresenter;
Ned Burnsdc10c952018-11-21 14:36:21 -050092 private NotificationListenerService.RankingMap mLatestRankingMap;
Ned Burnsd35708c2019-01-09 15:38:03 -050093 @VisibleForTesting
Eliot Courtneya6d8cf22017-10-20 13:26:58 +090094 protected NotificationData mNotificationData;
Ned Burnsf36c6252019-01-09 19:12:33 -050095
Ned Burnsdc10c952018-11-21 14:36:21 -050096 @VisibleForTesting
97 final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
Kevina5ff1fa2018-08-21 16:35:48 -070098 = new ArrayList<>();
Gus Prevasd65c2db2018-12-18 17:13:38 -050099 private final List<NotificationEntryListener> mNotificationEntryListeners = new ArrayList<>();
Mady Mellorc2ff0112019-03-28 14:18:06 -0700100 private NotificationRemoveInterceptor mRemoveInterceptor;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900101
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900102 @Override
103 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
104 pw.println("NotificationEntryManager state:");
105 pw.print(" mPendingNotifications=");
106 if (mPendingNotifications.size() == 0) {
107 pw.println("null");
108 } else {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500109 for (NotificationEntry entry : mPendingNotifications.values()) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900110 pw.println(entry.notification);
111 }
112 }
Ned Burnsa5dad552019-01-29 17:59:10 -0500113 pw.println(" Lifetime-extended notifications:");
114 if (mRetainedNotifications.isEmpty()) {
115 pw.println(" None");
116 } else {
117 for (Map.Entry<NotificationEntry, NotificationLifetimeExtender> entry
118 : mRetainedNotifications.entrySet()) {
119 pw.println(" " + entry.getKey().notification + " retained by "
120 + entry.getValue().getClass().getName());
121 }
122 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900123 }
124
Govinda Wasserman3e7ce552019-08-13 11:35:44 -0400125 @Inject
Eliot Courtney6c313d32017-12-14 19:57:51 +0900126 public NotificationEntryManager(Context context) {
Jason Monk297c04e2018-08-23 17:16:59 -0400127 mNotificationData = new NotificationData();
Jason Monk297c04e2018-08-23 17:16:59 -0400128 }
129
Gus Prevasd65c2db2018-12-18 17:13:38 -0500130 /** Adds a {@link NotificationEntryListener}. */
131 public void addNotificationEntryListener(NotificationEntryListener listener) {
132 mNotificationEntryListeners.add(listener);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900133 }
134
Mady Mellorc2ff0112019-03-28 14:18:06 -0700135 /** Sets the {@link NotificationRemoveInterceptor}. */
136 public void setNotificationRemoveInterceptor(NotificationRemoveInterceptor interceptor) {
137 mRemoveInterceptor = interceptor;
138 }
139
Jason Monk297c04e2018-08-23 17:16:59 -0400140 /**
141 * Our dependencies can have cyclic references, so some need to be lazy
142 */
143 private NotificationRemoteInputManager getRemoteInputManager() {
144 if (mRemoteInputManager == null) {
145 mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
146 }
147 return mRemoteInputManager;
148 }
149
Ned Burnsc5864672019-02-20 12:57:29 -0500150 public void setRowBinder(NotificationRowBinder notificationRowBinder) {
Ned Burnsef2ef6c2019-01-02 16:48:08 -0500151 mNotificationRowBinder = notificationRowBinder;
152 }
153
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900154 public void setUpWithPresenter(NotificationPresenter presenter,
Gus Prevas8621bd22018-12-20 15:04:25 -0500155 NotificationListContainer listContainer,
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900156 HeadsUpManager headsUpManager) {
157 mPresenter = presenter;
Gus Prevas92586462019-01-04 16:06:12 -0500158 mNotificationData.setHeadsUpManager(headsUpManager);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900159 }
160
Gus Prevas5c84abb2018-12-28 16:41:49 -0500161 /** Adds multiple {@link NotificationLifetimeExtender}s. */
162 public void addNotificationLifetimeExtenders(List<NotificationLifetimeExtender> extenders) {
163 for (NotificationLifetimeExtender extender : extenders) {
164 addNotificationLifetimeExtender(extender);
165 }
166 }
167
168 /** Adds a {@link NotificationLifetimeExtender}. */
169 public void addNotificationLifetimeExtender(NotificationLifetimeExtender extender) {
170 mNotificationLifetimeExtenders.add(extender);
Mady Mellor1a4e86f2019-05-03 16:07:23 -0700171 extender.setCallback(key -> removeNotification(key, mLatestRankingMap,
172 UNDEFINED_DISMISS_REASON));
Gus Prevas5c84abb2018-12-28 16:41:49 -0500173 }
174
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900175 public NotificationData getNotificationData() {
176 return mNotificationData;
177 }
178
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900179 @Override
180 public void onReorderingAllowed() {
181 updateNotifications();
182 }
183
Mady Mellorc2ff0112019-03-28 14:18:06 -0700184 /**
185 * Requests a notification to be removed.
186 *
187 * @param n the notification to remove.
188 * @param reason why it is being removed e.g. {@link NotificationListenerService#REASON_CANCEL},
189 * or 0 if unknown.
190 */
191 public void performRemoveNotification(StatusBarNotification n, int reason) {
Selim Cinek7bc9be72019-03-06 18:42:05 -0800192 final NotificationVisibility nv = obtainVisibility(n.getKey());
Gus Prevasca1b6f72018-12-28 10:53:11 -0500193 removeNotificationInternal(
Mady Mellorc2ff0112019-03-28 14:18:06 -0700194 n.getKey(), null, nv, false /* forceRemove */, true /* removedByUser */,
195 reason);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900196 }
197
Selim Cinek7bc9be72019-03-06 18:42:05 -0800198 private NotificationVisibility obtainVisibility(String key) {
199 final int rank = mNotificationData.getRank(key);
200 final int count = mNotificationData.getActiveNotifications().size();
201 NotificationVisibility.NotificationLocation location =
202 NotificationLogger.getNotificationLocation(getNotificationData().get(key));
203 return NotificationVisibility.obtain(key, rank, count, true, location);
204 }
205
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900206 private void abortExistingInflation(String key) {
207 if (mPendingNotifications.containsKey(key)) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500208 NotificationEntry entry = mPendingNotifications.get(key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900209 entry.abortTask();
210 mPendingNotifications.remove(key);
211 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500212 NotificationEntry addedEntry = mNotificationData.get(key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900213 if (addedEntry != null) {
214 addedEntry.abortTask();
215 }
216 }
217
Ned Burnsdc10c952018-11-21 14:36:21 -0500218 /**
219 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
220 * about the failure.
221 *
222 * WARNING: this will call back into us. Don't hold any locks.
223 */
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900224 @Override
Ned Burnsdc10c952018-11-21 14:36:21 -0500225 public void handleInflationException(StatusBarNotification n, Exception e) {
Gus Prevasdca2be52018-12-21 11:25:10 -0500226 removeNotificationInternal(
Mady Mellorc2ff0112019-03-28 14:18:06 -0700227 n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */,
228 REASON_ERROR);
Gus Prevasca1b6f72018-12-28 10:53:11 -0500229 for (NotificationEntryListener listener : mNotificationEntryListeners) {
230 listener.onInflationError(n, e);
Ned Burnsdc10c952018-11-21 14:36:21 -0500231 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900232 }
233
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900234 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -0500235 public void onAsyncInflationFinished(NotificationEntry entry,
Kevind4660b22018-09-27 10:57:35 -0700236 @InflationFlag int inflatedFlags) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900237 mPendingNotifications.remove(entry.key);
238 // If there was an async task started after the removal, we don't want to add it back to
239 // the list, otherwise we might get leaks.
Evan Laird94492852018-10-25 13:43:01 -0400240 if (!entry.isRowRemoved()) {
Kevin01a53cb2018-11-09 18:19:54 -0800241 boolean isNew = mNotificationData.get(entry.key) == null;
242 if (isNew) {
Gus Prevasb43dc652018-12-20 13:11:45 -0500243 for (NotificationEntryListener listener : mNotificationEntryListeners) {
244 listener.onEntryInflated(entry, inflatedFlags);
245 }
Ned Burns69760872019-01-03 15:36:38 -0500246 mNotificationData.add(entry);
Ned Burnsf36c6252019-01-09 19:12:33 -0500247 for (NotificationEntryListener listener : mNotificationEntryListeners) {
248 listener.onBeforeNotificationAdded(entry);
249 }
Ned Burns69760872019-01-03 15:36:38 -0500250 updateNotifications();
251 for (NotificationEntryListener listener : mNotificationEntryListeners) {
252 listener.onNotificationAdded(entry);
253 }
Kevin01a53cb2018-11-09 18:19:54 -0800254 } else {
Gus Prevasd65c2db2018-12-18 17:13:38 -0500255 for (NotificationEntryListener listener : mNotificationEntryListeners) {
256 listener.onEntryReinflated(entry);
Ned Burns3d6b3962018-12-07 21:26:00 -0500257 }
Kevin01a53cb2018-11-09 18:19:54 -0800258 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900259 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900260 }
261
262 @Override
Mady Mellorc2ff0112019-03-28 14:18:06 -0700263 public void removeNotification(String key, NotificationListenerService.RankingMap ranking,
264 int reason) {
Selim Cinek7bc9be72019-03-06 18:42:05 -0800265 removeNotificationInternal(key, ranking, obtainVisibility(key), false /* forceRemove */,
Mady Mellorc2ff0112019-03-28 14:18:06 -0700266 false /* removedByUser */, reason);
Kevina5ff1fa2018-08-21 16:35:48 -0700267 }
268
Gus Prevasdca2be52018-12-21 11:25:10 -0500269 private void removeNotificationInternal(
270 String key,
271 @Nullable NotificationListenerService.RankingMap ranking,
Gus Prevasca1b6f72018-12-28 10:53:11 -0500272 @Nullable NotificationVisibility visibility,
Gus Prevasdca2be52018-12-21 11:25:10 -0500273 boolean forceRemove,
Mady Mellorc2ff0112019-03-28 14:18:06 -0700274 boolean removedByUser,
275 int reason) {
276
277 if (mRemoveInterceptor != null
278 && mRemoveInterceptor.onNotificationRemoveRequested(key, reason)) {
279 // Remove intercepted; skip
280 return;
281 }
282
Ned Burnsf81c4c42019-01-07 14:10:43 -0500283 final NotificationEntry entry = mNotificationData.get(key);
Evan Laird7c6df8d2019-09-18 20:09:34 +0000284
285 abortExistingInflation(key);
286
Gus Prevasf37435a2018-12-20 15:40:01 -0500287 boolean lifetimeExtended = false;
288
Gus Prevasd65c2db2018-12-18 17:13:38 -0500289 if (entry != null) {
Gus Prevasf37435a2018-12-20 15:40:01 -0500290 // If a manager needs to keep the notification around for whatever reason, we
291 // keep the notification
Selim Cinek7bc9be72019-03-06 18:42:05 -0800292 boolean entryDismissed = entry.isRowDismissed();
293 if (!forceRemove && !entryDismissed) {
Gus Prevasf37435a2018-12-20 15:40:01 -0500294 for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
295 if (extender.shouldExtendLifetime(entry)) {
296 mLatestRankingMap = ranking;
Ned Burnsa5dad552019-01-29 17:59:10 -0500297 extendLifetime(entry, extender);
Gus Prevasf37435a2018-12-20 15:40:01 -0500298 lifetimeExtended = true;
299 break;
300 }
Kevina5ff1fa2018-08-21 16:35:48 -0700301 }
302 }
Gus Prevasf37435a2018-12-20 15:40:01 -0500303
304 if (!lifetimeExtended) {
305 // At this point, we are guaranteed the notification will be removed
306
307 // Ensure any managers keeping the lifetime extended stop managing the entry
Ned Burnsa5dad552019-01-29 17:59:10 -0500308 cancelLifetimeExtension(entry);
Gus Prevasf37435a2018-12-20 15:40:01 -0500309
Gus Prevasf37435a2018-12-20 15:40:01 -0500310 if (entry.rowExists()) {
311 entry.removeRow();
Gus Prevasf37435a2018-12-20 15:40:01 -0500312 }
313
314 // Let's remove the children if this was a summary
315 handleGroupSummaryRemoved(key);
316
Ned Burns69760872019-01-03 15:36:38 -0500317 mNotificationData.remove(key, ranking);
318 updateNotifications();
319 Dependency.get(LeakDetector.class).trackGarbage(entry);
Selim Cinek7bc9be72019-03-06 18:42:05 -0800320 removedByUser |= entryDismissed;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900321
Ned Burnsef2ef6c2019-01-02 16:48:08 -0500322 for (NotificationEntryListener listener : mNotificationEntryListeners) {
323 listener.onEntryRemoved(entry, visibility, removedByUser);
324 }
325 }
Gus Prevas8621bd22018-12-20 15:04:25 -0500326 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900327 }
328
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900329 /**
330 * Ensures that the group children are cancelled immediately when the group summary is cancelled
331 * instead of waiting for the notification manager to send all cancels. Otherwise this could
332 * lead to flickers.
333 *
334 * This also ensures that the animation looks nice and only consists of a single disappear
335 * animation instead of multiple.
336 * @param key the key of the notification was removed
337 *
338 */
339 private void handleGroupSummaryRemoved(String key) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500340 NotificationEntry entry = mNotificationData.get(key);
Evan Laird94492852018-10-25 13:43:01 -0400341 if (entry != null && entry.rowExists() && entry.isSummaryWithChildren()) {
342 if (entry.notification.getOverrideGroupKey() != null && !entry.isRowDismissed()) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900343 // We don't want to remove children for autobundled notifications as they are not
344 // always cancelled. We only remove them if they were dismissed by the user.
345 return;
346 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500347 List<NotificationEntry> childEntries = entry.getChildren();
Evan Laird94492852018-10-25 13:43:01 -0400348 if (childEntries == null) {
349 return;
350 }
351 for (int i = 0; i < childEntries.size(); i++) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500352 NotificationEntry childEntry = childEntries.get(i);
Evan Laird94492852018-10-25 13:43:01 -0400353 boolean isForeground = (entry.notification.getNotification().flags
Selim Cinek038e2592018-08-16 16:30:45 -0700354 & Notification.FLAG_FOREGROUND_SERVICE) != 0;
Kevina5ff1fa2018-08-21 16:35:48 -0700355 boolean keepForReply =
Jason Monk297c04e2018-08-23 17:16:59 -0400356 getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
357 || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
Selim Cinek038e2592018-08-16 16:30:45 -0700358 if (isForeground || keepForReply) {
359 // the child is a foreground service notification which we can't remove or it's
360 // a child we're keeping around for reply!
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900361 continue;
362 }
Evan Lairdce2d1af2019-05-30 16:00:22 -0400363 childEntry.setKeepInParent(true);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900364 // we need to set this state earlier as otherwise we might generate some weird
365 // animations
Evan Lairdce2d1af2019-05-30 16:00:22 -0400366 childEntry.removeRow();
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900367 }
368 }
369 }
370
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900371 private void addNotificationInternal(StatusBarNotification notification,
Tony Mak628cb932018-06-19 18:30:41 +0100372 NotificationListenerService.RankingMap rankingMap) throws InflationException {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900373 String key = notification.getKey();
Kevina97ea052018-09-11 13:53:18 -0700374 if (DEBUG) {
375 Log.d(TAG, "addNotification key=" + key);
376 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900377
Tony Mak628cb932018-06-19 18:30:41 +0100378 mNotificationData.updateRanking(rankingMap);
379 NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
380 rankingMap.getRanking(key, ranking);
Ned Burns69760872019-01-03 15:36:38 -0500381
Ned Burnsf81c4c42019-01-07 14:10:43 -0500382 NotificationEntry entry = new NotificationEntry(notification, ranking);
Ned Burns69760872019-01-03 15:36:38 -0500383
384 Dependency.get(LeakDetector.class).trackInstance(entry);
385 // Construct the expanded view.
Mady Mellorc2ff0112019-03-28 14:18:06 -0700386 requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
387 REASON_CANCEL));
Ned Burns69760872019-01-03 15:36:38 -0500388
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900389 abortExistingInflation(key);
390
Gus Prevasd65c2db2018-12-18 17:13:38 -0500391 mPendingNotifications.put(key, entry);
392 for (NotificationEntryListener listener : mNotificationEntryListeners) {
393 listener.onPendingEntryAdded(entry);
Ned Burns3d6b3962018-12-07 21:26:00 -0500394 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900395 }
396
397 @Override
398 public void addNotification(StatusBarNotification notification,
399 NotificationListenerService.RankingMap ranking) {
400 try {
401 addNotificationInternal(notification, ranking);
402 } catch (InflationException e) {
403 handleInflationException(notification, e);
404 }
405 }
406
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900407 private void updateNotificationInternal(StatusBarNotification notification,
408 NotificationListenerService.RankingMap ranking) throws InflationException {
409 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
410
411 final String key = notification.getKey();
412 abortExistingInflation(key);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500413 NotificationEntry entry = mNotificationData.get(key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900414 if (entry == null) {
415 return;
416 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900417
Kevina5ff1fa2018-08-21 16:35:48 -0700418 // Notification is updated so it is essentially re-added and thus alive again. Don't need
Kevine9e938c2018-09-06 13:38:11 -0700419 // to keep its lifetime extended.
Ned Burnsa5dad552019-01-29 17:59:10 -0500420 cancelLifetimeExtension(entry);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900421
Gus Prevas26d91272018-12-21 16:43:28 -0500422 mNotificationData.update(entry, ranking, notification);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900423
Mady Mellor0ad5b9d2019-01-08 14:59:55 -0800424 for (NotificationEntryListener listener : mNotificationEntryListeners) {
425 listener.onPreEntryUpdated(entry);
426 }
427
Mady Mellorc2ff0112019-03-28 14:18:06 -0700428 requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
429 REASON_CANCEL));
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900430 updateNotifications();
431
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900432 if (DEBUG) {
433 // Is this for you?
Jason Monk297c04e2018-08-23 17:16:59 -0400434 boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
435 .isNotificationForCurrentProfiles(notification);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900436 Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
437 }
438
Gus Prevasb43dc652018-12-20 13:11:45 -0500439 for (NotificationEntryListener listener : mNotificationEntryListeners) {
Mady Mellor0ad5b9d2019-01-08 14:59:55 -0800440 listener.onPostEntryUpdated(entry);
Gus Prevasb43dc652018-12-20 13:11:45 -0500441 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900442 }
443
444 @Override
445 public void updateNotification(StatusBarNotification notification,
446 NotificationListenerService.RankingMap ranking) {
447 try {
448 updateNotificationInternal(notification, ranking);
449 } catch (InflationException e) {
450 handleInflationException(notification, e);
451 }
452 }
453
454 public void updateNotifications() {
455 mNotificationData.filterAndSort();
Ned Burnsf36c6252019-01-09 19:12:33 -0500456 if (mPresenter != null) {
457 mPresenter.updateNotificationViews();
458 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900459 }
460
Ned Burns6b19b082019-02-12 18:50:47 -0500461 @Override
Tony Mak628cb932018-06-19 18:30:41 +0100462 public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500463 List<NotificationEntry> entries = new ArrayList<>();
Tony Mak628cb932018-06-19 18:30:41 +0100464 entries.addAll(mNotificationData.getActiveNotifications());
465 entries.addAll(mPendingNotifications.values());
466
467 // Has a copy of the current UI adjustments.
468 ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
Gus Prevas33fbc152018-12-04 13:17:32 -0500469 ArrayMap<String, Integer> oldImportances = new ArrayMap<>();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500470 for (NotificationEntry entry : entries) {
Tony Mak628cb932018-06-19 18:30:41 +0100471 NotificationUiAdjustment adjustment =
472 NotificationUiAdjustment.extractFromNotificationEntry(entry);
473 oldAdjustments.put(entry.key, adjustment);
Gus Prevas33fbc152018-12-04 13:17:32 -0500474 oldImportances.put(entry.key, entry.importance);
Tony Mak628cb932018-06-19 18:30:41 +0100475 }
476
477 // Populate notification entries from the new rankings.
478 mNotificationData.updateRanking(rankingMap);
479 updateRankingOfPendingNotifications(rankingMap);
480
481 // By comparing the old and new UI adjustments, reinflate the view accordingly.
Ned Burnsf81c4c42019-01-07 14:10:43 -0500482 for (NotificationEntry entry : entries) {
Ned Burnsc5864672019-02-20 12:57:29 -0500483 requireBinder().onNotificationRankingUpdated(
Gus Prevas8ba88a82018-12-18 11:13:44 -0500484 entry,
485 oldImportances.get(entry.key),
486 oldAdjustments.get(entry.key),
Ned Burnsf529ec22019-02-12 19:33:50 -0500487 NotificationUiAdjustment.extractFromNotificationEntry(entry));
Tony Mak628cb932018-06-19 18:30:41 +0100488 }
489
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900490 updateNotifications();
Mark Renoufbbcf07f2019-05-09 10:42:43 -0400491
492 for (NotificationEntryListener listener : mNotificationEntryListeners) {
493 listener.onNotificationRankingUpdated(rankingMap);
494 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900495 }
496
Tony Mak628cb932018-06-19 18:30:41 +0100497 private void updateRankingOfPendingNotifications(
498 @Nullable NotificationListenerService.RankingMap rankingMap) {
499 if (rankingMap == null) {
500 return;
501 }
502 NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500503 for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
Tony Mak628cb932018-06-19 18:30:41 +0100504 rankingMap.getRanking(pendingNotification.key, tmpRanking);
505 pendingNotification.populateFromRanking(tmpRanking);
506 }
507 }
508
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900509 /**
Ned Burns3d6b3962018-12-07 21:26:00 -0500510 * @return An iterator for all "pending" notifications. Pending notifications are newly-posted
511 * notifications whose views have not yet been inflated. In general, the system pretends like
512 * these don't exist, although there are a couple exceptions.
513 */
Ned Burnsf81c4c42019-01-07 14:10:43 -0500514 public Iterable<NotificationEntry> getPendingNotificationsIterator() {
Ned Burns3d6b3962018-12-07 21:26:00 -0500515 return mPendingNotifications.values();
516 }
Ned Burnsa5dad552019-01-29 17:59:10 -0500517
518 private void extendLifetime(NotificationEntry entry, NotificationLifetimeExtender extender) {
519 NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
520 if (activeExtender != null && activeExtender != extender) {
521 activeExtender.setShouldManageLifetime(entry, false);
522 }
523 mRetainedNotifications.put(entry, extender);
524 extender.setShouldManageLifetime(entry, true);
525 }
526
527 private void cancelLifetimeExtension(NotificationEntry entry) {
528 NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
529 if (activeExtender != null) {
530 activeExtender.setShouldManageLifetime(entry, false);
531 }
532 }
Ned Burnsc5864672019-02-20 12:57:29 -0500533
534 private NotificationRowBinder requireBinder() {
535 if (mNotificationRowBinder == null) {
536 throw new RuntimeException("You must initialize NotificationEntryManager by calling"
537 + "setRowBinder() before using.");
538 }
539 return mNotificationRowBinder;
540 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900541}