blob: 87841a5a413f37846d7828a5cb0dea2de19c1d1d [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
Selim Cinekffd3dc62018-11-30 18:06:57 -0800159 if (isMenuVisible(row)) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900160 row.animateTranslateNotification(0);
161 return;
Selim Cinekffd3dc62018-11-30 18:06:57 -0800162 } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
163 row.getNotificationParent().animateTranslateNotification(0);
164 return;
165 } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
166 // We never want to open the app directly if the user clicks in between
167 // the notifications.
168 return;
169 }
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900170
171 // Mark notification for one frame.
172 row.setJustClicked(true);
173 DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
174
175 mCallback.onNotificationClicked(sbn, row);
176 }
177
Selim Cinekffd3dc62018-11-30 18:06:57 -0800178 private boolean isMenuVisible(ExpandableNotificationRow row) {
179 return row.getProvider() != null && row.getProvider().isMenuVisible();
180 }
181
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900182 public void register(ExpandableNotificationRow row, StatusBarNotification sbn) {
183 Notification notification = sbn.getNotification();
184 if (notification.contentIntent != null || notification.fullScreenIntent != null) {
185 row.setOnClickListener(this);
186 } else {
187 row.setOnClickListener(null);
188 }
189 }
190 }
191
192 private final DeviceProvisionedController.DeviceProvisionedListener
193 mDeviceProvisionedListener =
194 new DeviceProvisionedController.DeviceProvisionedListener() {
195 @Override
196 public void onDeviceProvisionedChanged() {
197 updateNotifications();
198 }
199 };
200
201 public NotificationListenerService.RankingMap getLatestRankingMap() {
202 return mLatestRankingMap;
203 }
204
205 public void setLatestRankingMap(NotificationListenerService.RankingMap latestRankingMap) {
206 mLatestRankingMap = latestRankingMap;
207 }
208
209 public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
210 mDisableNotificationAlerts = disableNotificationAlerts;
211 mHeadsUpObserver.onChange(true);
212 }
213
214 public void destroy() {
215 mDeviceProvisionedController.removeCallback(mDeviceProvisionedListener);
216 }
217
218 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
219 if (!isHeadsUp && mHeadsUpEntriesToRemoveOnSwitch.contains(entry)) {
220 removeNotification(entry.key, getLatestRankingMap());
221 mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
222 if (mHeadsUpEntriesToRemoveOnSwitch.isEmpty()) {
223 setLatestRankingMap(null);
224 }
225 } else {
226 updateNotificationRanking(null);
227 }
228 }
229
230 @Override
231 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
232 pw.println("NotificationEntryManager state:");
233 pw.print(" mPendingNotifications=");
234 if (mPendingNotifications.size() == 0) {
235 pw.println("null");
236 } else {
237 for (NotificationData.Entry entry : mPendingNotifications.values()) {
238 pw.println(entry.notification);
239 }
240 }
241 pw.print(" mUseHeadsUp=");
242 pw.println(mUseHeadsUp);
Kenny Guy8cc15d22018-05-09 09:50:55 +0100243 pw.print(" mKeysKeptForRemoteInput: ");
244 pw.println(mKeysKeptForRemoteInput);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900245 }
246
Eliot Courtney6c313d32017-12-14 19:57:51 +0900247 public NotificationEntryManager(Context context) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900248 mContext = context;
249 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
250 mBarService = IStatusBarService.Stub.asInterface(
251 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
252 mMessagingUtil = new NotificationMessagingUtil(context);
253 mSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
Selim Cinekf93bf3e2018-05-08 14:43:21 -0700254 mGroupManager.setPendingEntries(mPendingNotifications);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900255 }
256
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900257 public void setUpWithPresenter(NotificationPresenter presenter,
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900258 NotificationListContainer listContainer, Callback callback,
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900259 HeadsUpManager headsUpManager) {
260 mPresenter = presenter;
261 mCallback = callback;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900262 mNotificationData = new NotificationData(presenter);
263 mHeadsUpManager = headsUpManager;
264 mNotificationData.setHeadsUpManager(mHeadsUpManager);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900265 mListContainer = listContainer;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900266
267 mHeadsUpObserver = new ContentObserver(mPresenter.getHandler()) {
268 @Override
269 public void onChange(boolean selfChange) {
270 boolean wasUsing = mUseHeadsUp;
271 mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
272 && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
273 mContext.getContentResolver(),
274 Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
275 Settings.Global.HEADS_UP_OFF);
276 Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
277 if (wasUsing != mUseHeadsUp) {
278 if (!mUseHeadsUp) {
279 Log.d(TAG,
280 "dismissing any existing heads up notification on disable event");
281 mHeadsUpManager.releaseAllImmediately();
282 }
283 }
284 }
285 };
286
287 if (ENABLE_HEADS_UP) {
288 mContext.getContentResolver().registerContentObserver(
289 Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
290 true,
291 mHeadsUpObserver);
292 mContext.getContentResolver().registerContentObserver(
293 Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
294 mHeadsUpObserver);
295 }
296
297 mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
298
299 mHeadsUpObserver.onChange(true); // set up
Julia Reynoldsb5867452018-02-28 16:31:35 -0500300 mOnAppOpsClickListener = mGutsManager::openGuts;
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900301 }
302
303 public NotificationData getNotificationData() {
304 return mNotificationData;
305 }
306
307 public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
308 return mGutsManager::openGuts;
309 }
310
311 @Override
312 public void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
313 mUiOffloadThread.submit(() -> {
314 try {
315 mBarService.onNotificationExpansionChanged(key, userAction, expanded);
316 } catch (RemoteException e) {
317 // Ignore.
318 }
319 });
320 }
321
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900322 @Override
323 public void onReorderingAllowed() {
324 updateNotifications();
325 }
326
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400327 private boolean shouldSuppressFullScreenIntent(NotificationData.Entry entry) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900328 if (mPresenter.isDeviceInVrMode()) {
329 return true;
330 }
331
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400332 return mNotificationData.shouldSuppressFullScreenIntent(entry);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900333 }
334
335 private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
336 PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
337 entry.notification.getUser().getIdentifier());
338
339 final StatusBarNotification sbn = entry.notification;
340 if (entry.row != null) {
341 entry.reset();
342 updateNotification(entry, pmUser, sbn, entry.row);
343 } else {
344 new RowInflaterTask().inflate(mContext, parent, entry,
345 row -> {
346 bindRow(entry, pmUser, sbn, row);
347 updateNotification(entry, pmUser, sbn, row);
348 });
349 }
350 }
351
352 private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
353 StatusBarNotification sbn, ExpandableNotificationRow row) {
354 row.setExpansionLogger(this, entry.notification.getKey());
355 row.setGroupManager(mGroupManager);
356 row.setHeadsUpManager(mHeadsUpManager);
357 row.setOnExpandClickListener(mPresenter);
358 row.setInflationCallback(this);
359 row.setLongPressListener(getNotificationLongClicker());
Selim Cinek8875de12018-03-22 10:14:32 -0700360 mListContainer.bindRow(row);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900361 mRemoteInputManager.bindRow(row);
362
363 // Get the app name.
364 // Note that Notification.Builder#bindHeaderAppName has similar logic
365 // but since this field is used in the guts, it must be accurate.
366 // Therefore we will only show the application label, or, failing that, the
367 // package name. No substitutions.
368 final String pkg = sbn.getPackageName();
369 String appname = pkg;
370 try {
371 final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
372 PackageManager.MATCH_UNINSTALLED_PACKAGES
373 | PackageManager.MATCH_DISABLED_COMPONENTS);
374 if (info != null) {
375 appname = String.valueOf(pmUser.getApplicationLabel(info));
376 }
377 } catch (PackageManager.NameNotFoundException e) {
378 // Do nothing
379 }
380 row.setAppName(appname);
381 row.setOnDismissRunnable(() ->
382 performRemoveNotification(row.getStatusBarNotification()));
383 row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
384 if (ENABLE_REMOTE_INPUT) {
385 row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
386 }
387
Julia Reynoldsb5867452018-02-28 16:31:35 -0500388 row.setAppOpsOnClickListener(mOnAppOpsClickListener);
389
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900390 mCallback.onBindRow(entry, pmUser, sbn, row);
391 }
392
393 public void performRemoveNotification(StatusBarNotification n) {
Dieter Hsud39f0d52018-04-14 02:08:30 +0800394 final int rank = mNotificationData.getRank(n.getKey());
395 final int count = mNotificationData.getActiveNotifications().size();
396 final NotificationVisibility nv = NotificationVisibility.obtain(n.getKey(), rank, count,
397 true);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900398 NotificationData.Entry entry = mNotificationData.get(n.getKey());
Kenny Guy8cc15d22018-05-09 09:50:55 +0100399
400 if (FORCE_REMOTE_INPUT_HISTORY
401 && mKeysKeptForRemoteInput.contains(n.getKey())) {
402 mKeysKeptForRemoteInput.remove(n.getKey());
403 }
404
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900405 mRemoteInputManager.onPerformRemoveNotification(n, entry);
406 final String pkg = n.getPackageName();
407 final String tag = n.getTag();
408 final int id = n.getId();
409 final int userId = n.getUserId();
410 try {
411 int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
412 if (isHeadsUp(n.getKey())) {
413 dismissalSurface = NotificationStats.DISMISSAL_PEEK;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900414 } else if (mListContainer.hasPulsingNotifications()) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900415 dismissalSurface = NotificationStats.DISMISSAL_AOD;
416 }
Dieter Hsud39f0d52018-04-14 02:08:30 +0800417 mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(), dismissalSurface, nv);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900418 removeNotification(n.getKey(), null);
419
420 } catch (RemoteException ex) {
421 // system process is dead if we're here.
422 }
423
424 mCallback.onPerformRemoveNotification(n);
425 }
426
427 /**
428 * Cancel this notification and tell the StatusBarManagerService / NotificationManagerService
429 * about the failure.
430 *
431 * WARNING: this will call back into us. Don't hold any locks.
432 */
433 void handleNotificationError(StatusBarNotification n, String message) {
434 removeNotification(n.getKey(), null);
435 try {
436 mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
437 n.getInitialPid(), message, n.getUserId());
438 } catch (RemoteException ex) {
439 // The end is nigh.
440 }
441 }
442
443 private void abortExistingInflation(String key) {
444 if (mPendingNotifications.containsKey(key)) {
445 NotificationData.Entry entry = mPendingNotifications.get(key);
446 entry.abortTask();
447 mPendingNotifications.remove(key);
448 }
449 NotificationData.Entry addedEntry = mNotificationData.get(key);
450 if (addedEntry != null) {
451 addedEntry.abortTask();
452 }
453 }
454
455 @Override
456 public void handleInflationException(StatusBarNotification notification, Exception e) {
457 handleNotificationError(notification, e.getMessage());
458 }
459
460 private void addEntry(NotificationData.Entry shadeEntry) {
461 boolean isHeadsUped = shouldPeek(shadeEntry);
462 if (isHeadsUped) {
463 mHeadsUpManager.showNotification(shadeEntry);
464 // Mark as seen immediately
465 setNotificationShown(shadeEntry.notification);
466 }
467 addNotificationViews(shadeEntry);
468 mCallback.onNotificationAdded(shadeEntry);
469 }
470
471 @Override
472 public void onAsyncInflationFinished(NotificationData.Entry entry) {
473 mPendingNotifications.remove(entry.key);
474 // If there was an async task started after the removal, we don't want to add it back to
475 // the list, otherwise we might get leaks.
476 boolean isNew = mNotificationData.get(entry.key) == null;
477 if (isNew && !entry.row.isRemoved()) {
478 addEntry(entry);
479 } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
480 mVisualStabilityManager.onLowPriorityUpdated(entry);
481 mPresenter.updateNotificationViews();
482 }
483 entry.row.setLowPriorityStateUpdated(false);
484 }
485
486 @Override
487 public void removeNotification(String key, NotificationListenerService.RankingMap ranking) {
488 boolean deferRemoval = false;
489 abortExistingInflation(key);
490 if (mHeadsUpManager.isHeadsUp(key)) {
491 // A cancel() in response to a remote input shouldn't be delayed, as it makes the
492 // sending look longer than it takes.
493 // Also we should not defer the removal if reordering isn't allowed since otherwise
494 // some notifications can't disappear before the panel is closed.
495 boolean ignoreEarliestRemovalTime = mRemoteInputManager.getController().isSpinning(key)
496 && !FORCE_REMOTE_INPUT_HISTORY
497 || !mVisualStabilityManager.isReorderingAllowed();
498 deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
499 }
500 mMediaManager.onNotificationRemoved(key);
501
502 NotificationData.Entry entry = mNotificationData.get(key);
Selim Cinek1397ea32018-01-16 17:34:52 -0800503 if (FORCE_REMOTE_INPUT_HISTORY
504 && shouldKeepForRemoteInput(entry)
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900505 && entry.row != null && !entry.row.isDismissed()) {
Selim Cinek1397ea32018-01-16 17:34:52 -0800506 CharSequence remoteInputText = entry.remoteInputText;
507 if (TextUtils.isEmpty(remoteInputText)) {
508 remoteInputText = entry.remoteInputTextWhenReset;
509 }
Kenny Guya0f6de82018-04-06 16:20:16 +0100510 StatusBarNotification newSbn = rebuildNotificationWithRemoteInput(entry,
511 remoteInputText, false /* showSpinner */);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900512 boolean updated = false;
Selim Cinek90d11a12018-01-17 16:24:36 -0800513 entry.onRemoteInputInserted();
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900514 try {
515 updateNotificationInternal(newSbn, null);
516 updated = true;
517 } catch (InflationException e) {
518 deferRemoval = false;
519 }
520 if (updated) {
521 Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
Kenny Guy8cc15d22018-05-09 09:50:55 +0100522 addKeyKeptForRemoteInput(entry.key);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900523 return;
524 }
525 }
Kenny Guy8cc15d22018-05-09 09:50:55 +0100526
527 if (FORCE_REMOTE_INPUT_HISTORY
528 && shouldKeepForSmartReply(entry)
529 && entry.row != null && !entry.row.isDismissed()) {
530 // Turn off the spinner and hide buttons when an app cancels the notification.
531 StatusBarNotification newSbn = rebuildNotificationForCanceledSmartReplies(entry);
532 boolean updated = false;
533 try {
534 updateNotificationInternal(newSbn, null);
535 updated = true;
536 } catch (InflationException e) {
537 // Ignore just don't keep the notification around.
538 }
539 // Treat the reply as longer sending.
540 mSmartReplyController.stopSending(entry);
541 if (updated) {
542 Log.w(TAG, "Keeping notification around after sending smart reply " + entry.key);
543 addKeyKeptForRemoteInput(entry.key);
544 return;
545 }
546 }
547
548 // Actually removing notification so smart reply controller can forget about it.
549 mSmartReplyController.stopSending(entry);
550
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900551 if (deferRemoval) {
552 mLatestRankingMap = ranking;
553 mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
554 return;
555 }
556
557 if (mRemoteInputManager.onRemoveNotification(entry)) {
558 mLatestRankingMap = ranking;
559 return;
560 }
561
562 if (entry != null && mGutsManager.getExposedGuts() != null
563 && mGutsManager.getExposedGuts() == entry.row.getGuts()
564 && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
565 Log.w(TAG, "Keeping notification because it's showing guts. " + key);
566 mLatestRankingMap = ranking;
567 mGutsManager.setKeyToRemoveOnGutsClosed(key);
568 return;
569 }
570
571 if (entry != null) {
572 mForegroundServiceController.removeNotification(entry.notification);
573 }
574
575 if (entry != null && entry.row != null) {
576 entry.row.setRemoved();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900577 mListContainer.cleanUpViewState(entry.row);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900578 }
579 // Let's remove the children if this was a summary
580 handleGroupSummaryRemoved(key);
581 StatusBarNotification old = removeNotificationViews(key, ranking);
582
583 mCallback.onNotificationRemoved(key, old);
584 }
585
Kenny Guya0f6de82018-04-06 16:20:16 +0100586 public StatusBarNotification rebuildNotificationWithRemoteInput(NotificationData.Entry entry,
587 CharSequence remoteInputText, boolean showSpinner) {
588 StatusBarNotification sbn = entry.notification;
589
590 Notification.Builder b = Notification.Builder
591 .recoverBuilder(mContext, sbn.getNotification().clone());
Kenny Guy8cc15d22018-05-09 09:50:55 +0100592 if (remoteInputText != null) {
593 CharSequence[] oldHistory = sbn.getNotification().extras
594 .getCharSequenceArray(Notification.EXTRA_REMOTE_INPUT_HISTORY);
595 CharSequence[] newHistory;
596 if (oldHistory == null) {
597 newHistory = new CharSequence[1];
598 } else {
599 newHistory = new CharSequence[oldHistory.length + 1];
600 System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
601 }
602 newHistory[0] = String.valueOf(remoteInputText);
603 b.setRemoteInputHistory(newHistory);
Kenny Guya0f6de82018-04-06 16:20:16 +0100604 }
Kenny Guya0f6de82018-04-06 16:20:16 +0100605 b.setShowRemoteInputSpinner(showSpinner);
Kenny Guy8cc15d22018-05-09 09:50:55 +0100606 b.setHideSmartReplies(true);
Kenny Guya0f6de82018-04-06 16:20:16 +0100607
608 Notification newNotification = b.build();
609
610 // Undo any compatibility view inflation
611 newNotification.contentView = sbn.getNotification().contentView;
612 newNotification.bigContentView = sbn.getNotification().bigContentView;
613 newNotification.headsUpContentView = sbn.getNotification().headsUpContentView;
614
615 StatusBarNotification newSbn = new StatusBarNotification(sbn.getPackageName(),
616 sbn.getOpPkg(),
617 sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
618 newNotification, sbn.getUser(), sbn.getOverrideGroupKey(), sbn.getPostTime());
619 return newSbn;
620 }
621
Kenny Guy8cc15d22018-05-09 09:50:55 +0100622 @VisibleForTesting
623 StatusBarNotification rebuildNotificationForCanceledSmartReplies(
624 NotificationData.Entry entry) {
625 return rebuildNotificationWithRemoteInput(entry, null /* remoteInputTest */,
626 false /* showSpinner */);
627 }
628
629 private boolean shouldKeepForSmartReply(NotificationData.Entry entry) {
630 return entry != null && mSmartReplyController.isSendingSmartReply(entry.key);
631 }
632
Selim Cinek1397ea32018-01-16 17:34:52 -0800633 private boolean shouldKeepForRemoteInput(NotificationData.Entry entry) {
634 if (entry == null) {
635 return false;
636 }
637 if (mRemoteInputManager.getController().isSpinning(entry.key)) {
638 return true;
639 }
640 if (entry.hasJustSentRemoteInput()) {
641 return true;
642 }
643 return false;
644 }
645
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900646 private StatusBarNotification removeNotificationViews(String key,
647 NotificationListenerService.RankingMap ranking) {
648 NotificationData.Entry entry = mNotificationData.remove(key, ranking);
649 if (entry == null) {
650 Log.w(TAG, "removeNotification for unknown key: " + key);
651 return null;
652 }
653 updateNotifications();
654 Dependency.get(LeakDetector.class).trackGarbage(entry);
655 return entry.notification;
656 }
657
658 /**
659 * Ensures that the group children are cancelled immediately when the group summary is cancelled
660 * instead of waiting for the notification manager to send all cancels. Otherwise this could
661 * lead to flickers.
662 *
663 * This also ensures that the animation looks nice and only consists of a single disappear
664 * animation instead of multiple.
665 * @param key the key of the notification was removed
666 *
667 */
668 private void handleGroupSummaryRemoved(String key) {
669 NotificationData.Entry entry = mNotificationData.get(key);
670 if (entry != null && entry.row != null
671 && entry.row.isSummaryWithChildren()) {
672 if (entry.notification.getOverrideGroupKey() != null && !entry.row.isDismissed()) {
673 // We don't want to remove children for autobundled notifications as they are not
674 // always cancelled. We only remove them if they were dismissed by the user.
675 return;
676 }
677 List<ExpandableNotificationRow> notificationChildren =
678 entry.row.getNotificationChildren();
679 for (int i = 0; i < notificationChildren.size(); i++) {
680 ExpandableNotificationRow row = notificationChildren.get(i);
Selim Cinek038e2592018-08-16 16:30:45 -0700681 NotificationData.Entry childEntry = row.getEntry();
682 boolean isForeground = (row.getStatusBarNotification().getNotification().flags
683 & Notification.FLAG_FOREGROUND_SERVICE) != 0;
684 boolean keepForReply = FORCE_REMOTE_INPUT_HISTORY
685 && (shouldKeepForRemoteInput(childEntry)
686 || shouldKeepForSmartReply(childEntry));
687 if (isForeground || keepForReply) {
688 // the child is a foreground service notification which we can't remove or it's
689 // a child we're keeping around for reply!
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900690 continue;
691 }
692 row.setKeepInParent(true);
693 // we need to set this state earlier as otherwise we might generate some weird
694 // animations
695 row.setRemoved();
696 }
697 }
698 }
699
700 public void updateNotificationsOnDensityOrFontScaleChanged() {
Dieter Hsu36e1ebc2018-06-06 15:41:46 +0800701 ArrayList<NotificationData.Entry> userNotifications =
702 mNotificationData.getNotificationsForCurrentUser();
703 for (int i = 0; i < userNotifications.size(); i++) {
704 NotificationData.Entry entry = userNotifications.get(i);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900705 boolean exposedGuts = mGutsManager.getExposedGuts() != null
706 && entry.row.getGuts() == mGutsManager.getExposedGuts();
707 entry.row.onDensityOrFontScaleChanged();
708 if (exposedGuts) {
yoshiki iguchia85c2a02018-01-12 11:28:06 +0900709 mGutsManager.onDensityOrFontScaleChanged(entry.row);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900710 }
711 }
712 }
713
Julia Reynoldsb5867452018-02-28 16:31:35 -0500714 protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900715 StatusBarNotification sbn, ExpandableNotificationRow row) {
716 row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
717 boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
718 boolean isUpdate = mNotificationData.get(entry.key) != null;
719 boolean wasLowPriority = row.isLowPriority();
720 row.setIsLowPriority(isLowPriority);
721 row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
722 // bind the click event to the content area
723 mNotificationClicker.register(row, sbn);
724
725 // Extract target SDK version.
726 try {
727 ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
728 entry.targetSdk = info.targetSdkVersion;
729 } catch (PackageManager.NameNotFoundException ex) {
730 Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
731 }
732 row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
733 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
734 entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
735 entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
736
737 entry.row = row;
738 entry.row.setOnActivatedListener(mPresenter);
739
740 boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
741 mNotificationData.getImportance(sbn.getKey()));
742 boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
743 && !mPresenter.isPresenterFullyCollapsed();
744 row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
745 row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
746 row.updateNotification(entry);
747 }
748
749
750 protected void addNotificationViews(NotificationData.Entry entry) {
751 if (entry == null) {
752 return;
753 }
754 // Add the expanded view and icon.
755 mNotificationData.add(entry);
Julia Reynolds91590062018-04-02 16:24:11 -0400756 tagForeground(entry.notification);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900757 updateNotifications();
758 }
759
760 protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
761 throws InflationException {
762 if (DEBUG) {
763 Log.d(TAG, "createNotificationViews(notification=" + sbn);
764 }
765 NotificationData.Entry entry = new NotificationData.Entry(sbn);
766 Dependency.get(LeakDetector.class).trackInstance(entry);
767 entry.createIcons(mContext, sbn);
768 // Construct the expanded view.
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900769 inflateViews(entry, mListContainer.getViewParentForNotification(entry));
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900770 return entry;
771 }
772
773 private void addNotificationInternal(StatusBarNotification notification,
774 NotificationListenerService.RankingMap ranking) throws InflationException {
775 String key = notification.getKey();
776 if (DEBUG) Log.d(TAG, "addNotification key=" + key);
777
778 mNotificationData.updateRanking(ranking);
779 NotificationData.Entry shadeEntry = createNotificationViews(notification);
780 boolean isHeadsUped = shouldPeek(shadeEntry);
781 if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400782 if (shouldSuppressFullScreenIntent(shadeEntry)) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900783 if (DEBUG) {
784 Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
785 }
786 } else if (mNotificationData.getImportance(key)
787 < NotificationManager.IMPORTANCE_HIGH) {
788 if (DEBUG) {
789 Log.d(TAG, "No Fullscreen intent: not important enough: "
790 + key);
791 }
792 } else {
793 // Stop screensaver if the notification has a fullscreen intent.
794 // (like an incoming phone call)
795 SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
796
797 // not immersive & a fullscreen alert should be shown
798 if (DEBUG)
799 Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
800 try {
801 EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
802 key);
803 notification.getNotification().fullScreenIntent.send();
804 shadeEntry.notifyFullScreenIntentLaunched();
805 mMetricsLogger.count("note_fullscreen", 1);
806 } catch (PendingIntent.CanceledException e) {
807 }
808 }
809 }
810 abortExistingInflation(key);
811
812 mForegroundServiceController.addNotification(notification,
813 mNotificationData.getImportance(key));
814
815 mPendingNotifications.put(key, shadeEntry);
Selim Cinekf93bf3e2018-05-08 14:43:21 -0700816 mGroupManager.onPendingEntryAdded(shadeEntry);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900817 }
818
Julia Reynolds91590062018-04-02 16:24:11 -0400819 @VisibleForTesting
820 protected void tagForeground(StatusBarNotification notification) {
821 ArraySet<Integer> activeOps = mForegroundServiceController.getAppOps(
822 notification.getUserId(), notification.getPackageName());
823 if (activeOps != null) {
824 int N = activeOps.size();
825 for (int i = 0; i < N; i++) {
826 updateNotificationsForAppOp(activeOps.valueAt(i), notification.getUid(),
827 notification.getPackageName(), true);
828 }
829 }
830 }
831
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900832 @Override
833 public void addNotification(StatusBarNotification notification,
834 NotificationListenerService.RankingMap ranking) {
835 try {
836 addNotificationInternal(notification, ranking);
837 } catch (InflationException e) {
838 handleInflationException(notification, e);
839 }
840 }
841
Julia Reynolds91590062018-04-02 16:24:11 -0400842 public void updateNotificationsForAppOp(int appOp, int uid, String pkg, boolean showIcon) {
843 String foregroundKey = mForegroundServiceController.getStandardLayoutKey(
844 UserHandle.getUserId(uid), pkg);
845 if (foregroundKey != null) {
846 mNotificationData.updateAppOp(appOp, uid, pkg, foregroundKey, showIcon);
Julia Reynoldsfc640012018-02-21 12:25:27 -0500847 updateNotifications();
848 }
849 }
850
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900851 private boolean alertAgain(NotificationData.Entry oldEntry, Notification newNotification) {
852 return oldEntry == null || !oldEntry.hasInterrupted()
853 || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
854 }
855
856 private void updateNotificationInternal(StatusBarNotification notification,
857 NotificationListenerService.RankingMap ranking) throws InflationException {
858 if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
859
860 final String key = notification.getKey();
861 abortExistingInflation(key);
862 NotificationData.Entry entry = mNotificationData.get(key);
863 if (entry == null) {
864 return;
865 }
866 mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
867 mRemoteInputManager.onUpdateNotification(entry);
Kenny Guy8cc15d22018-05-09 09:50:55 +0100868 mSmartReplyController.stopSending(entry);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900869
870 if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
871 mGutsManager.setKeyToRemoveOnGutsClosed(null);
872 Log.w(TAG, "Notification that was kept for guts was updated. " + key);
873 }
874
875 Notification n = notification.getNotification();
876 mNotificationData.updateRanking(ranking);
877
878 final StatusBarNotification oldNotification = entry.notification;
879 entry.notification = notification;
880 mGroupManager.onEntryUpdated(entry, oldNotification);
881
882 entry.updateIcons(mContext, notification);
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900883 inflateViews(entry, mListContainer.getViewParentForNotification(entry));
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900884
885 mForegroundServiceController.updateNotification(notification,
886 mNotificationData.getImportance(key));
887
888 boolean shouldPeek = shouldPeek(entry, notification);
889 boolean alertAgain = alertAgain(entry, n);
890
891 updateHeadsUp(key, entry, shouldPeek, alertAgain);
892 updateNotifications();
893
894 if (!notification.isClearable()) {
895 // The user may have performed a dismiss action on the notification, since it's
896 // not clearable we should snap it back.
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900897 mListContainer.snapViewIfNeeded(entry.row);
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900898 }
899
900 if (DEBUG) {
901 // Is this for you?
902 boolean isForCurrentUser = mPresenter.isNotificationForCurrentProfiles(notification);
903 Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
904 }
905
906 mCallback.onNotificationUpdated(notification);
907 }
908
909 @Override
910 public void updateNotification(StatusBarNotification notification,
911 NotificationListenerService.RankingMap ranking) {
912 try {
913 updateNotificationInternal(notification, ranking);
914 } catch (InflationException e) {
915 handleInflationException(notification, e);
916 }
917 }
918
919 public void updateNotifications() {
920 mNotificationData.filterAndSort();
921
922 mPresenter.updateNotificationViews();
923 }
924
925 public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
926 mNotificationData.updateRanking(ranking);
927 updateNotifications();
928 }
929
930 protected boolean shouldPeek(NotificationData.Entry entry) {
931 return shouldPeek(entry, entry.notification);
932 }
933
934 public boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn) {
935 if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
936 if (DEBUG) Log.d(TAG, "No peeking: no huns or vr mode");
937 return false;
938 }
939
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400940 if (mNotificationData.shouldFilterOut(entry)) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900941 if (DEBUG) Log.d(TAG, "No peeking: filtered notification: " + sbn.getKey());
942 return false;
943 }
944
945 boolean inUse = mPowerManager.isScreenOn() && !mSystemServicesProxy.isDreaming();
946
947 if (!inUse && !mPresenter.isDozing()) {
948 if (DEBUG) {
949 Log.d(TAG, "No peeking: not in use: " + sbn.getKey());
950 }
951 return false;
952 }
953
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400954 if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(entry)) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900955 if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
956 return false;
957 }
958
Julia Reynolds24653c32018-03-02 13:16:37 -0500959 // Peeking triggers an ambient display pulse, so disable peek is ambient is active
Julia Reynoldsaa96cf32018-04-17 09:09:04 -0400960 if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(entry)) {
Eliot Courtneya6d8cf22017-10-20 13:26:58 +0900961 if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
962 return false;
963 }
964
965 if (entry.hasJustLaunchedFullScreenIntent()) {
966 if (DEBUG) Log.d(TAG, "No peeking: recent fullscreen: " + sbn.getKey());
967 return false;
968 }
969
970 if (isSnoozedPackage(sbn)) {
971 if (DEBUG) Log.d(TAG, "No peeking: snoozed package: " + sbn.getKey());
972 return false;
973 }
974
975 // Allow peeking for DEFAULT notifications only if we're on Ambient Display.
976 int importanceLevel = mPresenter.isDozing() ? NotificationManager.IMPORTANCE_DEFAULT
977 : NotificationManager.IMPORTANCE_HIGH;
978 if (mNotificationData.getImportance(sbn.getKey()) < importanceLevel) {
979 if (DEBUG) Log.d(TAG, "No peeking: unimportant notification: " + sbn.getKey());
980 return false;
981 }
982
983 // Don't peek notifications that are suppressed due to group alert behavior
984 if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
985 if (DEBUG) Log.d(TAG, "No peeking: suppressed due to group alert behavior");
986 return false;
987 }
988
989 if (!mCallback.shouldPeek(entry, sbn)) {
990 return false;
991 }
992
993 return true;
994 }
995
996 protected void setNotificationShown(StatusBarNotification n) {
997 setNotificationsShown(new String[]{n.getKey()});
998 }
999
1000 protected void setNotificationsShown(String[] keys) {
1001 try {
1002 mNotificationListener.setNotificationsShown(keys);
1003 } catch (RuntimeException e) {
1004 Log.d(TAG, "failed setNotificationsShown: ", e);
1005 }
1006 }
1007
1008 protected boolean isSnoozedPackage(StatusBarNotification sbn) {
1009 return mHeadsUpManager.isSnoozed(sbn.getPackageName());
1010 }
1011
1012 protected void updateHeadsUp(String key, NotificationData.Entry entry, boolean shouldPeek,
1013 boolean alertAgain) {
1014 final boolean wasHeadsUp = isHeadsUp(key);
1015 if (wasHeadsUp) {
1016 if (!shouldPeek) {
1017 // We don't want this to be interrupting anymore, lets remove it
1018 mHeadsUpManager.removeNotification(key, false /* ignoreEarliestRemovalTime */);
1019 } else {
1020 mHeadsUpManager.updateNotification(entry, alertAgain);
1021 }
1022 } else if (shouldPeek && alertAgain) {
1023 // This notification was updated to be a heads-up, show it!
1024 mHeadsUpManager.showNotification(entry);
1025 }
1026 }
1027
1028 protected boolean isHeadsUp(String key) {
1029 return mHeadsUpManager.isHeadsUp(key);
1030 }
1031
Kenny Guy8cc15d22018-05-09 09:50:55 +01001032 public boolean isNotificationKeptForRemoteInput(String key) {
1033 return mKeysKeptForRemoteInput.contains(key);
1034 }
1035
1036 public void removeKeyKeptForRemoteInput(String key) {
1037 mKeysKeptForRemoteInput.remove(key);
1038 }
1039
1040 public void addKeyKeptForRemoteInput(String key) {
1041 if (FORCE_REMOTE_INPUT_HISTORY) {
1042 mKeysKeptForRemoteInput.add(key);
1043 }
1044 }
1045
Eliot Courtneya6d8cf22017-10-20 13:26:58 +09001046 /**
1047 * Callback for NotificationEntryManager.
1048 */
1049 public interface Callback {
1050
1051 /**
1052 * Called when a new entry is created.
1053 *
1054 * @param shadeEntry entry that was created
1055 */
1056 void onNotificationAdded(NotificationData.Entry shadeEntry);
1057
1058 /**
1059 * Called when a notification was updated.
1060 *
1061 * @param notification notification that was updated
1062 */
1063 void onNotificationUpdated(StatusBarNotification notification);
1064
1065 /**
1066 * Called when a notification was removed.
1067 *
1068 * @param key key of notification that was removed
1069 * @param old StatusBarNotification of the notification before it was removed
1070 */
1071 void onNotificationRemoved(String key, StatusBarNotification old);
1072
1073
1074 /**
1075 * Called when a notification is clicked.
1076 *
1077 * @param sbn notification that was clicked
1078 * @param row row for that notification
1079 */
1080 void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row);
1081
1082 /**
1083 * Called when a new notification and row is created.
1084 *
1085 * @param entry entry for the notification
1086 * @param pmUser package manager for user
1087 * @param sbn notification
1088 * @param row row for the notification
1089 */
1090 void onBindRow(NotificationData.Entry entry, PackageManager pmUser,
1091 StatusBarNotification sbn, ExpandableNotificationRow row);
1092
1093 /**
1094 * Removes a notification immediately.
1095 *
1096 * @param statusBarNotification notification that is being removed
1097 */
1098 void onPerformRemoveNotification(StatusBarNotification statusBarNotification);
1099
1100 /**
1101 * Returns true if NotificationEntryManager should peek this notification.
1102 *
1103 * @param entry entry of the notification that might be peeked
1104 * @param sbn notification that might be peeked
1105 * @return true if the notification should be peeked
1106 */
1107 boolean shouldPeek(NotificationData.Entry entry, StatusBarNotification sbn);
1108 }
1109}