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