blob: 027e8e426c4b0bbd6ba2bcd02a8f832127f45d59 [file] [log] [blame]
Ned Burnsf81c4c42019-01-07 14:10:43 -05001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.statusbar.notification.collection;
18
19import static android.app.Notification.CATEGORY_ALARM;
20import static android.app.Notification.CATEGORY_CALL;
21import static android.app.Notification.CATEGORY_EVENT;
22import static android.app.Notification.CATEGORY_MESSAGE;
23import static android.app.Notification.CATEGORY_REMINDER;
Mady Mellorfc02cc32019-04-01 14:47:55 -070024import static android.app.Notification.FLAG_BUBBLE;
Ned Burnsf81c4c42019-01-07 14:10:43 -050025import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
Mady Mellordf48d0a2019-06-25 18:26:46 -070026import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
Ned Burnsf81c4c42019-01-07 14:10:43 -050027import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
28import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
29import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
30import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
31
32import android.annotation.NonNull;
33import android.app.Notification;
34import android.app.NotificationChannel;
35import android.app.NotificationManager.Policy;
36import android.app.Person;
37import android.content.Context;
38import android.graphics.drawable.Icon;
39import android.os.Bundle;
40import android.os.Parcelable;
41import android.os.SystemClock;
42import android.service.notification.NotificationListenerService;
43import android.service.notification.SnoozeCriterion;
44import android.service.notification.StatusBarNotification;
45import android.util.ArraySet;
46import android.view.View;
47import android.widget.ImageView;
48
49import androidx.annotation.Nullable;
50
51import com.android.internal.annotations.VisibleForTesting;
52import com.android.internal.statusbar.StatusBarIcon;
53import com.android.internal.util.ArrayUtils;
54import com.android.internal.util.ContrastColorUtil;
55import com.android.systemui.statusbar.InflationTask;
56import com.android.systemui.statusbar.StatusBarIconView;
57import com.android.systemui.statusbar.notification.InflationException;
58import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Ned Burns1a5e22f2019-02-14 15:11:52 -050059import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
Ned Burnsf81c4c42019-01-07 14:10:43 -050060import com.android.systemui.statusbar.notification.row.NotificationGuts;
Ned Burnsf81c4c42019-01-07 14:10:43 -050061
62import java.util.ArrayList;
63import java.util.Collections;
64import java.util.List;
65import java.util.Objects;
66
67/**
68 * Represents a notification that the system UI knows about
69 *
70 * Whenever the NotificationManager tells us about the existence of a new notification, we wrap it
71 * in a NotificationEntry. Thus, every notification has an associated NotificationEntry, even if
72 * that notification is never displayed to the user (for example, if it's filtered out for some
73 * reason).
74 *
75 * Entries store information about the current state of the notification. Essentially:
76 * anything that needs to persist or be modifiable even when the notification's views don't
77 * exist. Any other state should be stored on the views/view controllers themselves.
78 *
79 * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
80 * clean this up in the future.
81 */
82public final class NotificationEntry {
83 private static final long LAUNCH_COOLDOWN = 2000;
84 private static final long REMOTE_INPUT_COOLDOWN = 500;
85 private static final long INITIALIZATION_DELAY = 400;
86 private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
87 private static final int COLOR_INVALID = 1;
88 public final String key;
89 public StatusBarNotification notification;
90 public NotificationChannel channel;
91 public long lastAudiblyAlertedMs;
92 public boolean noisy;
93 public boolean ambient;
94 public int importance;
95 public StatusBarIconView icon;
96 public StatusBarIconView expandedIcon;
Beverly40770652019-02-15 15:49:49 -050097 public StatusBarIconView centeredIcon;
Selim Cinek195dfc52019-05-30 19:35:05 -070098 public StatusBarIconView aodIcon;
Ned Burnsf81c4c42019-01-07 14:10:43 -050099 private boolean interruption;
100 public boolean autoRedacted; // whether the redacted notification was generated by us
101 public int targetSdk;
102 private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
103 public CharSequence remoteInputText;
104 public List<SnoozeCriterion> snoozeCriteria;
105 public int userSentiment = NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
106 /** Smart Actions provided by the NotificationAssistantService. */
107 @NonNull
108 public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
Gustav Senntone255f902019-01-31 13:28:02 +0000109 /** Smart replies provided by the NotificationAssistantService. */
110 @NonNull
111 public CharSequence[] systemGeneratedSmartReplies = new CharSequence[0];
Milo Sredkov13d88112019-02-01 12:23:24 +0000112
113 /**
114 * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
115 * currently editing a choice (smart reply), then this field contains the information about the
116 * suggestion being edited. Otherwise <code>null</code>.
117 */
118 public EditedSuggestionInfo editedSuggestionInfo;
119
Ned Burnsf81c4c42019-01-07 14:10:43 -0500120 @VisibleForTesting
121 public int suppressedVisualEffects;
122 public boolean suspended;
123
124 private NotificationEntry parent; // our parent (if we're in a group)
Ned Burnsf81c4c42019-01-07 14:10:43 -0500125 private ExpandableNotificationRow row; // the outer expanded view
126
127 private int mCachedContrastColor = COLOR_INVALID;
128 private int mCachedContrastColorIsFor = COLOR_INVALID;
129 private InflationTask mRunningTask = null;
130 private Throwable mDebugThrowable;
131 public CharSequence remoteInputTextWhenReset;
132 public long lastRemoteInputSent = NOT_LAUNCHED_YET;
133 public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
134 public CharSequence headsUpStatusBarText;
135 public CharSequence headsUpStatusBarTextPublic;
136
137 private long initializationTime = -1;
138
139 /**
140 * Whether or not this row represents a system notification. Note that if this is
141 * {@code null}, that means we were either unable to retrieve the info or have yet to
142 * retrieve the info.
143 */
144 public Boolean mIsSystemNotification;
145
146 /**
147 * Has the user sent a reply through this Notification.
148 */
149 private boolean hasSentReply;
150
151 /**
Julia Reynolds4509ce72019-01-31 13:12:43 -0500152 * Whether this notification has been approved globally, at the app level, and at the channel
153 * level for bubbling.
154 */
155 public boolean canBubble;
156
157 /**
Gus Prevascaed15c2019-01-18 14:19:51 -0500158 * Whether this notification is shown to the user as a high priority notification: visible on
159 * the lock screen/status bar and in the top section in the shade.
160 */
161 private boolean mHighPriority;
Ned Burns8c1b7632019-07-19 14:26:15 -0400162
163 private boolean mIsTopBucket;
164
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700165 private boolean mSensitive = true;
166 private Runnable mOnSensitiveChangedListener;
Selim Cineke3c6e462019-06-24 19:37:06 -0700167 private boolean mAutoHeadsUp;
Selim Cinek65c96f22019-07-25 20:09:04 -0700168 private boolean mPulseSupressed;
Gus Prevascaed15c2019-01-18 14:19:51 -0500169
Ned Burnsf81c4c42019-01-07 14:10:43 -0500170 public NotificationEntry(StatusBarNotification n) {
171 this(n, null);
172 }
173
174 public NotificationEntry(
175 StatusBarNotification n,
176 @Nullable NotificationListenerService.Ranking ranking) {
177 this.key = n.getKey();
178 this.notification = n;
179 if (ranking != null) {
180 populateFromRanking(ranking);
181 }
182 }
183
184 public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) {
185 channel = ranking.getChannel();
186 lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
187 importance = ranking.getImportance();
188 ambient = ranking.isAmbient();
189 snoozeCriteria = ranking.getSnoozeCriteria();
190 userSentiment = ranking.getUserSentiment();
191 systemGeneratedSmartActions = ranking.getSmartActions() == null
192 ? Collections.emptyList() : ranking.getSmartActions();
Gustav Senntone255f902019-01-31 13:28:02 +0000193 systemGeneratedSmartReplies = ranking.getSmartReplies() == null
Ned Burnsf81c4c42019-01-07 14:10:43 -0500194 ? new CharSequence[0]
195 : ranking.getSmartReplies().toArray(new CharSequence[0]);
196 suppressedVisualEffects = ranking.getSuppressedVisualEffects();
197 suspended = ranking.isSuspended();
Julia Reynolds4509ce72019-01-31 13:12:43 -0500198 canBubble = ranking.canBubble();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500199 }
200
201 public void setInterruption() {
202 interruption = true;
203 }
204
205 public boolean hasInterrupted() {
206 return interruption;
207 }
208
Gus Prevascaed15c2019-01-18 14:19:51 -0500209 public boolean isHighPriority() {
210 return mHighPriority;
211 }
212
213 public void setIsHighPriority(boolean highPriority) {
214 this.mHighPriority = highPriority;
215 }
216
Ned Burns8c1b7632019-07-19 14:26:15 -0400217 /**
218 * @return True if the notif should appear in the "top" or "important" section of notifications
219 * (as opposed to the "bottom" or "silent" section). This is usually the same as
220 * {@link #isHighPriority()}, but there are certain exceptions, such as media notifs.
221 */
222 public boolean isTopBucket() {
223 return mIsTopBucket;
224 }
225 public void setIsTopBucket(boolean isTopBucket) {
226 mIsTopBucket = isTopBucket;
227 }
228
Ned Burnsf81c4c42019-01-07 14:10:43 -0500229 public boolean isBubble() {
Mady Mellorfc02cc32019-04-01 14:47:55 -0700230 return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500231 }
232
Ned Burnsf81c4c42019-01-07 14:10:43 -0500233 /**
Mady Mellor9801e852019-01-22 14:50:28 -0800234 * Returns the data needed for a bubble for this notification, if it exists.
235 */
236 public Notification.BubbleMetadata getBubbleMetadata() {
237 return notification.getNotification().getBubbleMetadata();
238 }
239
240 /**
Ned Burnsf81c4c42019-01-07 14:10:43 -0500241 * Resets the notification entry to be re-used.
242 */
243 public void reset() {
244 if (row != null) {
245 row.reset();
246 }
247 }
248
249 public ExpandableNotificationRow getRow() {
250 return row;
251 }
252
253 //TODO: This will go away when we have a way to bind an entry to a row
254 public void setRow(ExpandableNotificationRow row) {
255 this.row = row;
256 }
257
258 @Nullable
259 public List<NotificationEntry> getChildren() {
Evan Lairdce2d1af2019-05-30 16:00:22 -0400260 if (row == null) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500261 return null;
262 }
263
Evan Lairdce2d1af2019-05-30 16:00:22 -0400264 List<ExpandableNotificationRow> rowChildren = row.getNotificationChildren();
265 if (rowChildren == null) {
266 return null;
267 }
268
269 ArrayList<NotificationEntry> children = new ArrayList<>();
270 for (ExpandableNotificationRow child : rowChildren) {
271 children.add(child.getEntry());
272 }
273
Ned Burnsf81c4c42019-01-07 14:10:43 -0500274 return children;
275 }
276
277 public void notifyFullScreenIntentLaunched() {
278 setInterruption();
279 lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
280 }
281
282 public boolean hasJustLaunchedFullScreenIntent() {
283 return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
284 }
285
286 public boolean hasJustSentRemoteInput() {
287 return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
288 }
289
290 public boolean hasFinishedInitialization() {
291 return initializationTime == -1
292 || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
293 }
294
295 /**
296 * Create the icons for a notification
297 * @param context the context to create the icons with
298 * @param sbn the notification
299 * @throws InflationException Exception if required icons are not valid or specified
300 */
301 public void createIcons(Context context, StatusBarNotification sbn)
302 throws InflationException {
303 Notification n = sbn.getNotification();
304 final Icon smallIcon = n.getSmallIcon();
305 if (smallIcon == null) {
306 throw new InflationException("No small icon in notification from "
307 + sbn.getPackageName());
308 }
309
310 // Construct the icon.
311 icon = new StatusBarIconView(context,
312 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
313 icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
314
315 // Construct the expanded icon.
316 expandedIcon = new StatusBarIconView(context,
317 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
318 expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
Beverly40770652019-02-15 15:49:49 -0500319
Selim Cinek195dfc52019-05-30 19:35:05 -0700320 // Construct the expanded icon.
321 aodIcon = new StatusBarIconView(context,
322 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
323 aodIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
324 aodIcon.setIncreasedSize(true);
325
Ned Burnsf81c4c42019-01-07 14:10:43 -0500326 final StatusBarIcon ic = new StatusBarIcon(
327 sbn.getUser(),
328 sbn.getPackageName(),
329 smallIcon,
330 n.iconLevel,
331 n.number,
332 StatusBarIconView.contentDescForNotification(context, n));
Beverly40770652019-02-15 15:49:49 -0500333
Selim Cinek195dfc52019-05-30 19:35:05 -0700334 if (!icon.set(ic) || !expandedIcon.set(ic) || !aodIcon.set(ic)) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500335 icon = null;
336 expandedIcon = null;
Beverly40770652019-02-15 15:49:49 -0500337 centeredIcon = null;
Selim Cinek195dfc52019-05-30 19:35:05 -0700338 aodIcon = null;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500339 throw new InflationException("Couldn't create icon: " + ic);
340 }
341 expandedIcon.setVisibility(View.INVISIBLE);
342 expandedIcon.setOnVisibilityChangedListener(
343 newVisibility -> {
344 if (row != null) {
345 row.setIconsVisible(newVisibility != View.VISIBLE);
346 }
347 });
Beverly40770652019-02-15 15:49:49 -0500348
349 // Construct the centered icon
350 if (notification.getNotification().isMediaNotification()) {
351 centeredIcon = new StatusBarIconView(context,
352 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
353 centeredIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
354
355 if (!centeredIcon.set(ic)) {
356 centeredIcon = null;
357 throw new InflationException("Couldn't update centered icon: " + ic);
358 }
359 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500360 }
361
362 public void setIconTag(int key, Object tag) {
363 if (icon != null) {
364 icon.setTag(key, tag);
365 expandedIcon.setTag(key, tag);
366 }
Beverly40770652019-02-15 15:49:49 -0500367
368 if (centeredIcon != null) {
369 centeredIcon.setTag(key, tag);
370 }
Selim Cinek195dfc52019-05-30 19:35:05 -0700371
372 if (aodIcon != null) {
373 aodIcon.setTag(key, tag);
374 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500375 }
376
377 /**
378 * Update the notification icons.
379 *
380 * @param context the context to create the icons with.
381 * @param sbn the notification to read the icon from.
382 * @throws InflationException Exception if required icons are not valid or specified
383 */
384 public void updateIcons(Context context, StatusBarNotification sbn)
385 throws InflationException {
386 if (icon != null) {
387 // Update the icon
388 Notification n = sbn.getNotification();
389 final StatusBarIcon ic = new StatusBarIcon(
390 notification.getUser(),
391 notification.getPackageName(),
392 n.getSmallIcon(),
393 n.iconLevel,
394 n.number,
395 StatusBarIconView.contentDescForNotification(context, n));
396 icon.setNotification(sbn);
397 expandedIcon.setNotification(sbn);
Selim Cinek195dfc52019-05-30 19:35:05 -0700398 aodIcon.setNotification(sbn);
399 if (!icon.set(ic) || !expandedIcon.set(ic) || !aodIcon.set(ic)) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500400 throw new InflationException("Couldn't update icon: " + ic);
401 }
Beverly40770652019-02-15 15:49:49 -0500402
403 if (centeredIcon != null) {
404 centeredIcon.setNotification(sbn);
405 if (!centeredIcon.set(ic)) {
406 throw new InflationException("Couldn't update centered icon: " + ic);
407 }
408 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500409 }
410 }
411
412 public int getContrastedColor(Context context, boolean isLowPriority,
413 int backgroundColor) {
414 int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
415 notification.getNotification().color;
416 if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
417 return mCachedContrastColor;
418 }
419 final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
420 backgroundColor);
421 mCachedContrastColorIsFor = rawColor;
422 mCachedContrastColor = contrasted;
423 return mCachedContrastColor;
424 }
425
426 /**
427 * Abort all existing inflation tasks
428 */
429 public void abortTask() {
430 if (mRunningTask != null) {
431 mRunningTask.abort();
432 mRunningTask = null;
433 }
434 }
435
436 public void setInflationTask(InflationTask abortableTask) {
437 // abort any existing inflation
438 InflationTask existing = mRunningTask;
439 abortTask();
440 mRunningTask = abortableTask;
441 if (existing != null && mRunningTask != null) {
442 mRunningTask.supersedeTask(existing);
443 }
444 }
445
446 public void onInflationTaskFinished() {
447 mRunningTask = null;
448 }
449
450 @VisibleForTesting
451 public InflationTask getRunningTask() {
452 return mRunningTask;
453 }
454
455 /**
456 * Set a throwable that is used for debugging
457 *
458 * @param debugThrowable the throwable to save
459 */
460 public void setDebugThrowable(Throwable debugThrowable) {
461 mDebugThrowable = debugThrowable;
462 }
463
464 public Throwable getDebugThrowable() {
465 return mDebugThrowable;
466 }
467
468 public void onRemoteInputInserted() {
469 lastRemoteInputSent = NOT_LAUNCHED_YET;
470 remoteInputTextWhenReset = null;
471 }
472
473 public void setHasSentReply() {
474 hasSentReply = true;
475 }
476
477 public boolean isLastMessageFromReply() {
478 if (!hasSentReply) {
479 return false;
480 }
481 Bundle extras = notification.getNotification().extras;
482 CharSequence[] replyTexts = extras.getCharSequenceArray(
483 Notification.EXTRA_REMOTE_INPUT_HISTORY);
484 if (!ArrayUtils.isEmpty(replyTexts)) {
485 return true;
486 }
487 Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
488 if (messages != null && messages.length > 0) {
489 Parcelable message = messages[messages.length - 1];
490 if (message instanceof Bundle) {
491 Notification.MessagingStyle.Message lastMessage =
492 Notification.MessagingStyle.Message.getMessageFromBundle(
493 (Bundle) message);
494 if (lastMessage != null) {
495 Person senderPerson = lastMessage.getSenderPerson();
496 if (senderPerson == null) {
497 return true;
498 }
499 Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
500 return Objects.equals(user, senderPerson);
501 }
502 }
503 }
504 return false;
505 }
506
507 public void setInitializationTime(long time) {
508 if (initializationTime == -1) {
509 initializationTime = time;
510 }
511 }
512
513 public void sendAccessibilityEvent(int eventType) {
514 if (row != null) {
515 row.sendAccessibilityEvent(eventType);
516 }
517 }
518
519 /**
520 * Used by NotificationMediaManager to determine... things
521 * @return {@code true} if we are a media notification
522 */
523 public boolean isMediaNotification() {
524 if (row == null) return false;
525
526 return row.isMediaRow();
527 }
528
529 /**
530 * We are a top level child if our parent is the list of notifications duh
531 * @return {@code true} if we're a top level notification
532 */
533 public boolean isTopLevelChild() {
534 return row != null && row.isTopLevelChild();
535 }
536
537 public void resetUserExpansion() {
538 if (row != null) row.resetUserExpansion();
539 }
540
Ned Burns1a5e22f2019-02-14 15:11:52 -0500541 public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500542 if (row != null) row.freeContentViewWhenSafe(inflationFlag);
543 }
544
Ned Burnsf81c4c42019-01-07 14:10:43 -0500545 public boolean rowExists() {
546 return row != null;
547 }
548
549 public boolean isRowDismissed() {
550 return row != null && row.isDismissed();
551 }
552
553 public boolean isRowRemoved() {
554 return row != null && row.isRemoved();
555 }
556
557 /**
558 * @return {@code true} if the row is null or removed
559 */
560 public boolean isRemoved() {
561 //TODO: recycling invalidates this
562 return row == null || row.isRemoved();
563 }
564
Ned Burnsf81c4c42019-01-07 14:10:43 -0500565 public boolean isRowPinned() {
566 return row != null && row.isPinned();
567 }
568
569 public void setRowPinned(boolean pinned) {
570 if (row != null) row.setPinned(pinned);
571 }
572
Ned Burnsf81c4c42019-01-07 14:10:43 -0500573 public boolean isRowHeadsUp() {
574 return row != null && row.isHeadsUp();
575 }
576
Selim Cinekb57dd8a2019-06-14 12:27:58 -0700577 public boolean showingPulsing() {
578 return row != null && row.showingPulsing();
579 }
580
Ned Burnsf81c4c42019-01-07 14:10:43 -0500581 public void setHeadsUp(boolean shouldHeadsUp) {
582 if (row != null) row.setHeadsUp(shouldHeadsUp);
583 }
584
Selim Cinekc3fec682019-06-06 18:11:07 -0700585 public void setHeadsUpAnimatingAway(boolean animatingAway) {
586 if (row != null) row.setHeadsUpAnimatingAway(animatingAway);
Selim Cinek459aee32019-02-20 11:18:56 -0800587 }
588
Selim Cineke3c6e462019-06-24 19:37:06 -0700589 /**
590 * Set that this notification was automatically heads upped. This happens for example when
591 * the user bypasses the lockscreen and media is playing.
592 */
593 public void setAutoHeadsUp(boolean autoHeadsUp) {
594 mAutoHeadsUp = autoHeadsUp;
595 }
596
597 /**
598 * @return if this notification was automatically heads upped. This happens for example when
599 * * the user bypasses the lockscreen and media is playing.
600 */
601 public boolean isAutoHeadsUp() {
602 return mAutoHeadsUp;
603 }
Selim Cinek459aee32019-02-20 11:18:56 -0800604
Ned Burnsf81c4c42019-01-07 14:10:43 -0500605 public boolean mustStayOnScreen() {
606 return row != null && row.mustStayOnScreen();
607 }
608
609 public void setHeadsUpIsVisible() {
610 if (row != null) row.setHeadsUpIsVisible();
611 }
612
613 //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
614 public ExpandableNotificationRow getHeadsUpAnimationView() {
615 return row;
616 }
617
618 public void setUserLocked(boolean userLocked) {
619 if (row != null) row.setUserLocked(userLocked);
620 }
621
622 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
623 if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
624 }
625
626 public void setGroupExpansionChanging(boolean changing) {
627 if (row != null) row.setGroupExpansionChanging(changing);
628 }
629
630 public void notifyHeightChanged(boolean needsAnimation) {
631 if (row != null) row.notifyHeightChanged(needsAnimation);
632 }
633
634 public void closeRemoteInput() {
635 if (row != null) row.closeRemoteInput();
636 }
637
638 public boolean areChildrenExpanded() {
639 return row != null && row.areChildrenExpanded();
640 }
641
642 public boolean keepInParent() {
643 return row != null && row.keepInParent();
644 }
645
646 //TODO: probably less confusing to say "is group fully visible"
647 public boolean isGroupNotFullyVisible() {
648 return row == null || row.isGroupNotFullyVisible();
649 }
650
651 public NotificationGuts getGuts() {
652 if (row != null) return row.getGuts();
653 return null;
654 }
655
Ned Burnsf81c4c42019-01-07 14:10:43 -0500656 public void removeRow() {
657 if (row != null) row.setRemoved();
658 }
659
660 public boolean isSummaryWithChildren() {
661 return row != null && row.isSummaryWithChildren();
662 }
663
664 public void setKeepInParent(boolean keep) {
665 if (row != null) row.setKeepInParent(keep);
666 }
667
668 public void onDensityOrFontScaleChanged() {
669 if (row != null) row.onDensityOrFontScaleChanged();
670 }
671
672 public boolean areGutsExposed() {
673 return row != null && row.getGuts() != null && row.getGuts().isExposed();
674 }
675
676 public boolean isChildInGroup() {
677 return parent == null;
678 }
679
Ned Burnsf81c4c42019-01-07 14:10:43 -0500680 /**
681 * @return Can the underlying notification be cleared? This can be different from whether the
682 * notification can be dismissed in case notifications are sensitive on the lockscreen.
683 * @see #canViewBeDismissed()
684 */
685 public boolean isClearable() {
686 if (notification == null || !notification.isClearable()) {
687 return false;
688 }
Evan Lairdce2d1af2019-05-30 16:00:22 -0400689
690 List<NotificationEntry> children = getChildren();
691 if (children != null && children.size() > 0) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500692 for (int i = 0; i < children.size(); i++) {
693 NotificationEntry child = children.get(i);
694 if (!child.isClearable()) {
695 return false;
696 }
697 }
698 }
699 return true;
700 }
701
702 public boolean canViewBeDismissed() {
703 if (row == null) return true;
704 return row.canViewBeDismissed();
705 }
706
707 @VisibleForTesting
708 boolean isExemptFromDndVisualSuppression() {
709 if (isNotificationBlockedByPolicy(notification.getNotification())) {
710 return false;
711 }
712
713 if ((notification.getNotification().flags
714 & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
715 return true;
716 }
717 if (notification.getNotification().isMediaNotification()) {
718 return true;
719 }
720 if (mIsSystemNotification != null && mIsSystemNotification) {
721 return true;
722 }
723 return false;
724 }
725
726 private boolean shouldSuppressVisualEffect(int effect) {
727 if (isExemptFromDndVisualSuppression()) {
728 return false;
729 }
730 return (suppressedVisualEffects & effect) != 0;
731 }
732
733 /**
734 * Returns whether {@link Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
735 * is set for this entry.
736 */
737 public boolean shouldSuppressFullScreenIntent() {
738 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
739 }
740
741 /**
742 * Returns whether {@link Policy#SUPPRESSED_EFFECT_PEEK}
743 * is set for this entry.
744 */
745 public boolean shouldSuppressPeek() {
746 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
747 }
748
749 /**
750 * Returns whether {@link Policy#SUPPRESSED_EFFECT_STATUS_BAR}
751 * is set for this entry.
752 */
753 public boolean shouldSuppressStatusBar() {
754 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
755 }
756
757 /**
758 * Returns whether {@link Policy#SUPPRESSED_EFFECT_AMBIENT}
759 * is set for this entry.
760 */
761 public boolean shouldSuppressAmbient() {
762 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
763 }
764
765 /**
766 * Returns whether {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
767 * is set for this entry.
768 */
769 public boolean shouldSuppressNotificationList() {
770 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
771 }
772
Mady Mellordf48d0a2019-06-25 18:26:46 -0700773
774 /**
775 * Returns whether {@link Policy#SUPPRESSED_EFFECT_BADGE}
776 * is set for this entry. This badge is not an app badge, but rather an indicator of "unseen"
777 * content. Typically this is referred to as a "dot" internally in Launcher & SysUI code.
778 */
779 public boolean shouldSuppressNotificationDot() {
780 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_BADGE);
781 }
782
Ned Burnsf81c4c42019-01-07 14:10:43 -0500783 /**
784 * Categories that are explicitly called out on DND settings screens are always blocked, if
785 * DND has flagged them, even if they are foreground or system notifications that might
786 * otherwise visually bypass DND.
787 */
788 private static boolean isNotificationBlockedByPolicy(Notification n) {
789 return isCategory(CATEGORY_CALL, n)
790 || isCategory(CATEGORY_MESSAGE, n)
791 || isCategory(CATEGORY_ALARM, n)
792 || isCategory(CATEGORY_EVENT, n)
793 || isCategory(CATEGORY_REMINDER, n);
794 }
795
796 private static boolean isCategory(String category, Notification n) {
797 return Objects.equals(n.category, category);
798 }
Milo Sredkov13d88112019-02-01 12:23:24 +0000799
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700800 /**
801 * Set this notification to be sensitive.
802 *
803 * @param sensitive true if the content of this notification is sensitive right now
804 * @param deviceSensitive true if the device in general is sensitive right now
805 */
806 public void setSensitive(boolean sensitive, boolean deviceSensitive) {
807 getRow().setSensitive(sensitive, deviceSensitive);
808 if (sensitive != mSensitive) {
809 mSensitive = sensitive;
810 if (mOnSensitiveChangedListener != null) {
811 mOnSensitiveChangedListener.run();
812 }
813 }
814 }
815
816 public boolean isSensitive() {
817 return mSensitive;
818 }
819
820 public void setOnSensitiveChangedListener(Runnable listener) {
821 mOnSensitiveChangedListener = listener;
822 }
823
Selim Cinek65c96f22019-07-25 20:09:04 -0700824 public boolean isPulseSuppressed() {
825 return mPulseSupressed;
826 }
827
828 public void setPulseSuppressed(boolean suppressed) {
829 mPulseSupressed = suppressed;
830 }
831
Milo Sredkov13d88112019-02-01 12:23:24 +0000832 /** Information about a suggestion that is being edited. */
833 public static class EditedSuggestionInfo {
834
835 /**
836 * The value of the suggestion (before any user edits).
837 */
838 public final CharSequence originalText;
839
840 /**
841 * The index of the suggestion that is being edited.
842 */
843 public final int index;
844
845 public EditedSuggestionInfo(CharSequence originalText, int index) {
846 this.originalText = originalText;
847 this.index = index;
848 }
849 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500850}