blob: f1373d14240210279ea0e79335ad56f7d132a7f2 [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;
24import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
25import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
26import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
27import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
28import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
29
30import android.annotation.NonNull;
31import android.app.Notification;
32import android.app.NotificationChannel;
33import android.app.NotificationManager.Policy;
34import android.app.Person;
35import android.content.Context;
36import android.graphics.drawable.Icon;
37import android.os.Bundle;
38import android.os.Parcelable;
39import android.os.SystemClock;
40import android.service.notification.NotificationListenerService;
41import android.service.notification.SnoozeCriterion;
42import android.service.notification.StatusBarNotification;
43import android.util.ArraySet;
44import android.view.View;
45import android.widget.ImageView;
46
47import androidx.annotation.Nullable;
48
49import com.android.internal.annotations.VisibleForTesting;
50import com.android.internal.statusbar.StatusBarIcon;
51import com.android.internal.util.ArrayUtils;
52import com.android.internal.util.ContrastColorUtil;
53import com.android.systemui.statusbar.InflationTask;
54import com.android.systemui.statusbar.StatusBarIconView;
55import com.android.systemui.statusbar.notification.InflationException;
56import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Ned Burns1a5e22f2019-02-14 15:11:52 -050057import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
Ned Burnsf81c4c42019-01-07 14:10:43 -050058import com.android.systemui.statusbar.notification.row.NotificationGuts;
Ned Burnsf81c4c42019-01-07 14:10:43 -050059
60import java.util.ArrayList;
61import java.util.Collections;
62import java.util.List;
63import java.util.Objects;
64
65/**
66 * Represents a notification that the system UI knows about
67 *
68 * Whenever the NotificationManager tells us about the existence of a new notification, we wrap it
69 * in a NotificationEntry. Thus, every notification has an associated NotificationEntry, even if
70 * that notification is never displayed to the user (for example, if it's filtered out for some
71 * reason).
72 *
73 * Entries store information about the current state of the notification. Essentially:
74 * anything that needs to persist or be modifiable even when the notification's views don't
75 * exist. Any other state should be stored on the views/view controllers themselves.
76 *
77 * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
78 * clean this up in the future.
79 */
80public final class NotificationEntry {
81 private static final long LAUNCH_COOLDOWN = 2000;
82 private static final long REMOTE_INPUT_COOLDOWN = 500;
83 private static final long INITIALIZATION_DELAY = 400;
84 private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
85 private static final int COLOR_INVALID = 1;
86 public final String key;
87 public StatusBarNotification notification;
88 public NotificationChannel channel;
89 public long lastAudiblyAlertedMs;
90 public boolean noisy;
91 public boolean ambient;
92 public int importance;
93 public StatusBarIconView icon;
94 public StatusBarIconView expandedIcon;
95 private boolean interruption;
96 public boolean autoRedacted; // whether the redacted notification was generated by us
97 public int targetSdk;
98 private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
99 public CharSequence remoteInputText;
100 public List<SnoozeCriterion> snoozeCriteria;
101 public int userSentiment = NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
102 /** Smart Actions provided by the NotificationAssistantService. */
103 @NonNull
104 public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
Gustav Senntone255f902019-01-31 13:28:02 +0000105 /** Smart replies provided by the NotificationAssistantService. */
106 @NonNull
107 public CharSequence[] systemGeneratedSmartReplies = new CharSequence[0];
Milo Sredkov13d88112019-02-01 12:23:24 +0000108
109 /**
110 * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
111 * currently editing a choice (smart reply), then this field contains the information about the
112 * suggestion being edited. Otherwise <code>null</code>.
113 */
114 public EditedSuggestionInfo editedSuggestionInfo;
115
Ned Burnsf81c4c42019-01-07 14:10:43 -0500116 @VisibleForTesting
117 public int suppressedVisualEffects;
118 public boolean suspended;
119
120 private NotificationEntry parent; // our parent (if we're in a group)
121 private ArrayList<NotificationEntry> children = new ArrayList<NotificationEntry>();
122 private ExpandableNotificationRow row; // the outer expanded view
123
124 private int mCachedContrastColor = COLOR_INVALID;
125 private int mCachedContrastColorIsFor = COLOR_INVALID;
126 private InflationTask mRunningTask = null;
127 private Throwable mDebugThrowable;
128 public CharSequence remoteInputTextWhenReset;
129 public long lastRemoteInputSent = NOT_LAUNCHED_YET;
130 public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
131 public CharSequence headsUpStatusBarText;
132 public CharSequence headsUpStatusBarTextPublic;
133
134 private long initializationTime = -1;
135
136 /**
137 * Whether or not this row represents a system notification. Note that if this is
138 * {@code null}, that means we were either unable to retrieve the info or have yet to
139 * retrieve the info.
140 */
141 public Boolean mIsSystemNotification;
142
143 /**
144 * Has the user sent a reply through this Notification.
145 */
146 private boolean hasSentReply;
147
148 /**
149 * Whether this notification should be displayed as a bubble.
150 */
151 private boolean mIsBubble;
152
153 /**
Julia Reynolds4509ce72019-01-31 13:12:43 -0500154 * Whether this notification has been approved globally, at the app level, and at the channel
155 * level for bubbling.
156 */
157 public boolean canBubble;
158
159 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800160 * Whether this notification should be shown in the shade when it is also displayed as a bubble.
161 *
162 * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
163 * expanded</p>
164 */
165 private boolean mShowInShadeWhenBubble;
166
167 /**
Ned Burnsf81c4c42019-01-07 14:10:43 -0500168 * Whether the user has dismissed this notification when it was in bubble form.
169 */
170 private boolean mUserDismissedBubble;
171
Gus Prevascaed15c2019-01-18 14:19:51 -0500172 /**
173 * Whether this notification is shown to the user as a high priority notification: visible on
174 * the lock screen/status bar and in the top section in the shade.
175 */
176 private boolean mHighPriority;
177
Ned Burnsf81c4c42019-01-07 14:10:43 -0500178 public NotificationEntry(StatusBarNotification n) {
179 this(n, null);
180 }
181
182 public NotificationEntry(
183 StatusBarNotification n,
184 @Nullable NotificationListenerService.Ranking ranking) {
185 this.key = n.getKey();
186 this.notification = n;
187 if (ranking != null) {
188 populateFromRanking(ranking);
189 }
190 }
191
192 public void populateFromRanking(@NonNull NotificationListenerService.Ranking ranking) {
193 channel = ranking.getChannel();
194 lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
195 importance = ranking.getImportance();
196 ambient = ranking.isAmbient();
197 snoozeCriteria = ranking.getSnoozeCriteria();
198 userSentiment = ranking.getUserSentiment();
199 systemGeneratedSmartActions = ranking.getSmartActions() == null
200 ? Collections.emptyList() : ranking.getSmartActions();
Gustav Senntone255f902019-01-31 13:28:02 +0000201 systemGeneratedSmartReplies = ranking.getSmartReplies() == null
Ned Burnsf81c4c42019-01-07 14:10:43 -0500202 ? new CharSequence[0]
203 : ranking.getSmartReplies().toArray(new CharSequence[0]);
204 suppressedVisualEffects = ranking.getSuppressedVisualEffects();
205 suspended = ranking.isSuspended();
Julia Reynolds4509ce72019-01-31 13:12:43 -0500206 canBubble = ranking.canBubble();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500207 }
208
209 public void setInterruption() {
210 interruption = true;
211 }
212
213 public boolean hasInterrupted() {
214 return interruption;
215 }
216
Gus Prevascaed15c2019-01-18 14:19:51 -0500217 public boolean isHighPriority() {
218 return mHighPriority;
219 }
220
221 public void setIsHighPriority(boolean highPriority) {
222 this.mHighPriority = highPriority;
223 }
224
Ned Burnsf81c4c42019-01-07 14:10:43 -0500225 public void setIsBubble(boolean bubbleable) {
226 mIsBubble = bubbleable;
227 }
228
229 public boolean isBubble() {
230 return mIsBubble;
231 }
232
233 public void setBubbleDismissed(boolean userDismissed) {
234 mUserDismissedBubble = userDismissed;
235 }
236
237 public boolean isBubbleDismissed() {
238 return mUserDismissedBubble;
239 }
240
241 /**
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800242 * Sets whether this notification should be shown in the shade when it is also displayed as a
243 * bubble.
244 */
245 public void setShowInShadeWhenBubble(boolean showInShade) {
246 mShowInShadeWhenBubble = showInShade;
247 }
248
249 /**
250 * Whether this notification should be shown in the shade when it is also displayed as a
251 * bubble.
252 */
253 public boolean showInShadeWhenBubble() {
254 // We always show it in the shade if non-clearable
255 return !isClearable() || mShowInShadeWhenBubble;
256 }
257
258 /**
Mady Mellor9801e852019-01-22 14:50:28 -0800259 * Returns the data needed for a bubble for this notification, if it exists.
260 */
261 public Notification.BubbleMetadata getBubbleMetadata() {
262 return notification.getNotification().getBubbleMetadata();
263 }
264
265 /**
Ned Burnsf81c4c42019-01-07 14:10:43 -0500266 * Resets the notification entry to be re-used.
267 */
268 public void reset() {
269 if (row != null) {
270 row.reset();
271 }
272 }
273
274 public ExpandableNotificationRow getRow() {
275 return row;
276 }
277
278 //TODO: This will go away when we have a way to bind an entry to a row
279 public void setRow(ExpandableNotificationRow row) {
280 this.row = row;
281 }
282
283 @Nullable
284 public List<NotificationEntry> getChildren() {
285 if (children.size() <= 0) {
286 return null;
287 }
288
289 return children;
290 }
291
292 public void notifyFullScreenIntentLaunched() {
293 setInterruption();
294 lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
295 }
296
297 public boolean hasJustLaunchedFullScreenIntent() {
298 return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
299 }
300
301 public boolean hasJustSentRemoteInput() {
302 return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
303 }
304
305 public boolean hasFinishedInitialization() {
306 return initializationTime == -1
307 || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
308 }
309
310 /**
311 * Create the icons for a notification
312 * @param context the context to create the icons with
313 * @param sbn the notification
314 * @throws InflationException Exception if required icons are not valid or specified
315 */
316 public void createIcons(Context context, StatusBarNotification sbn)
317 throws InflationException {
318 Notification n = sbn.getNotification();
319 final Icon smallIcon = n.getSmallIcon();
320 if (smallIcon == null) {
321 throw new InflationException("No small icon in notification from "
322 + sbn.getPackageName());
323 }
324
325 // Construct the icon.
326 icon = new StatusBarIconView(context,
327 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
328 icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
329
330 // Construct the expanded icon.
331 expandedIcon = new StatusBarIconView(context,
332 sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
333 expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
334 final StatusBarIcon ic = new StatusBarIcon(
335 sbn.getUser(),
336 sbn.getPackageName(),
337 smallIcon,
338 n.iconLevel,
339 n.number,
340 StatusBarIconView.contentDescForNotification(context, n));
341 if (!icon.set(ic) || !expandedIcon.set(ic)) {
342 icon = null;
343 expandedIcon = null;
344 throw new InflationException("Couldn't create icon: " + ic);
345 }
346 expandedIcon.setVisibility(View.INVISIBLE);
347 expandedIcon.setOnVisibilityChangedListener(
348 newVisibility -> {
349 if (row != null) {
350 row.setIconsVisible(newVisibility != View.VISIBLE);
351 }
352 });
353 }
354
355 public void setIconTag(int key, Object tag) {
356 if (icon != null) {
357 icon.setTag(key, tag);
358 expandedIcon.setTag(key, tag);
359 }
360 }
361
362 /**
363 * Update the notification icons.
364 *
365 * @param context the context to create the icons with.
366 * @param sbn the notification to read the icon from.
367 * @throws InflationException Exception if required icons are not valid or specified
368 */
369 public void updateIcons(Context context, StatusBarNotification sbn)
370 throws InflationException {
371 if (icon != null) {
372 // Update the icon
373 Notification n = sbn.getNotification();
374 final StatusBarIcon ic = new StatusBarIcon(
375 notification.getUser(),
376 notification.getPackageName(),
377 n.getSmallIcon(),
378 n.iconLevel,
379 n.number,
380 StatusBarIconView.contentDescForNotification(context, n));
381 icon.setNotification(sbn);
382 expandedIcon.setNotification(sbn);
383 if (!icon.set(ic) || !expandedIcon.set(ic)) {
384 throw new InflationException("Couldn't update icon: " + ic);
385 }
386 }
387 }
388
389 public int getContrastedColor(Context context, boolean isLowPriority,
390 int backgroundColor) {
391 int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
392 notification.getNotification().color;
393 if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
394 return mCachedContrastColor;
395 }
396 final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
397 backgroundColor);
398 mCachedContrastColorIsFor = rawColor;
399 mCachedContrastColor = contrasted;
400 return mCachedContrastColor;
401 }
402
403 /**
404 * Abort all existing inflation tasks
405 */
406 public void abortTask() {
407 if (mRunningTask != null) {
408 mRunningTask.abort();
409 mRunningTask = null;
410 }
411 }
412
413 public void setInflationTask(InflationTask abortableTask) {
414 // abort any existing inflation
415 InflationTask existing = mRunningTask;
416 abortTask();
417 mRunningTask = abortableTask;
418 if (existing != null && mRunningTask != null) {
419 mRunningTask.supersedeTask(existing);
420 }
421 }
422
423 public void onInflationTaskFinished() {
424 mRunningTask = null;
425 }
426
427 @VisibleForTesting
428 public InflationTask getRunningTask() {
429 return mRunningTask;
430 }
431
432 /**
433 * Set a throwable that is used for debugging
434 *
435 * @param debugThrowable the throwable to save
436 */
437 public void setDebugThrowable(Throwable debugThrowable) {
438 mDebugThrowable = debugThrowable;
439 }
440
441 public Throwable getDebugThrowable() {
442 return mDebugThrowable;
443 }
444
445 public void onRemoteInputInserted() {
446 lastRemoteInputSent = NOT_LAUNCHED_YET;
447 remoteInputTextWhenReset = null;
448 }
449
450 public void setHasSentReply() {
451 hasSentReply = true;
452 }
453
454 public boolean isLastMessageFromReply() {
455 if (!hasSentReply) {
456 return false;
457 }
458 Bundle extras = notification.getNotification().extras;
459 CharSequence[] replyTexts = extras.getCharSequenceArray(
460 Notification.EXTRA_REMOTE_INPUT_HISTORY);
461 if (!ArrayUtils.isEmpty(replyTexts)) {
462 return true;
463 }
464 Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
465 if (messages != null && messages.length > 0) {
466 Parcelable message = messages[messages.length - 1];
467 if (message instanceof Bundle) {
468 Notification.MessagingStyle.Message lastMessage =
469 Notification.MessagingStyle.Message.getMessageFromBundle(
470 (Bundle) message);
471 if (lastMessage != null) {
472 Person senderPerson = lastMessage.getSenderPerson();
473 if (senderPerson == null) {
474 return true;
475 }
476 Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
477 return Objects.equals(user, senderPerson);
478 }
479 }
480 }
481 return false;
482 }
483
484 public void setInitializationTime(long time) {
485 if (initializationTime == -1) {
486 initializationTime = time;
487 }
488 }
489
490 public void sendAccessibilityEvent(int eventType) {
491 if (row != null) {
492 row.sendAccessibilityEvent(eventType);
493 }
494 }
495
496 /**
497 * Used by NotificationMediaManager to determine... things
498 * @return {@code true} if we are a media notification
499 */
500 public boolean isMediaNotification() {
501 if (row == null) return false;
502
503 return row.isMediaRow();
504 }
505
506 /**
507 * We are a top level child if our parent is the list of notifications duh
508 * @return {@code true} if we're a top level notification
509 */
510 public boolean isTopLevelChild() {
511 return row != null && row.isTopLevelChild();
512 }
513
514 public void resetUserExpansion() {
515 if (row != null) row.resetUserExpansion();
516 }
517
Ned Burns1a5e22f2019-02-14 15:11:52 -0500518 public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500519 if (row != null) row.freeContentViewWhenSafe(inflationFlag);
520 }
521
522 public void setAmbientPulsing(boolean pulsing) {
523 if (row != null) row.setAmbientPulsing(pulsing);
524 }
525
526 public boolean rowExists() {
527 return row != null;
528 }
529
530 public boolean isRowDismissed() {
531 return row != null && row.isDismissed();
532 }
533
534 public boolean isRowRemoved() {
535 return row != null && row.isRemoved();
536 }
537
538 /**
539 * @return {@code true} if the row is null or removed
540 */
541 public boolean isRemoved() {
542 //TODO: recycling invalidates this
543 return row == null || row.isRemoved();
544 }
545
Ned Burnsf81c4c42019-01-07 14:10:43 -0500546 public boolean isRowPinned() {
547 return row != null && row.isPinned();
548 }
549
550 public void setRowPinned(boolean pinned) {
551 if (row != null) row.setPinned(pinned);
552 }
553
554 public boolean isRowAnimatingAway() {
555 return row != null && row.isHeadsUpAnimatingAway();
556 }
557
558 public boolean isRowHeadsUp() {
559 return row != null && row.isHeadsUp();
560 }
561
562 public void setHeadsUp(boolean shouldHeadsUp) {
563 if (row != null) row.setHeadsUp(shouldHeadsUp);
564 }
565
Selim Cinek459aee32019-02-20 11:18:56 -0800566
567 public void setAmbientGoingAway(boolean goingAway) {
568 if (row != null) row.setAmbientGoingAway(goingAway);
569 }
570
571
Ned Burnsf81c4c42019-01-07 14:10:43 -0500572 public boolean mustStayOnScreen() {
573 return row != null && row.mustStayOnScreen();
574 }
575
576 public void setHeadsUpIsVisible() {
577 if (row != null) row.setHeadsUpIsVisible();
578 }
579
580 //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
581 public ExpandableNotificationRow getHeadsUpAnimationView() {
582 return row;
583 }
584
585 public void setUserLocked(boolean userLocked) {
586 if (row != null) row.setUserLocked(userLocked);
587 }
588
589 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
590 if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
591 }
592
593 public void setGroupExpansionChanging(boolean changing) {
594 if (row != null) row.setGroupExpansionChanging(changing);
595 }
596
597 public void notifyHeightChanged(boolean needsAnimation) {
598 if (row != null) row.notifyHeightChanged(needsAnimation);
599 }
600
601 public void closeRemoteInput() {
602 if (row != null) row.closeRemoteInput();
603 }
604
605 public boolean areChildrenExpanded() {
606 return row != null && row.areChildrenExpanded();
607 }
608
609 public boolean keepInParent() {
610 return row != null && row.keepInParent();
611 }
612
613 //TODO: probably less confusing to say "is group fully visible"
614 public boolean isGroupNotFullyVisible() {
615 return row == null || row.isGroupNotFullyVisible();
616 }
617
618 public NotificationGuts getGuts() {
619 if (row != null) return row.getGuts();
620 return null;
621 }
622
Ned Burnsf81c4c42019-01-07 14:10:43 -0500623 public void removeRow() {
624 if (row != null) row.setRemoved();
625 }
626
627 public boolean isSummaryWithChildren() {
628 return row != null && row.isSummaryWithChildren();
629 }
630
631 public void setKeepInParent(boolean keep) {
632 if (row != null) row.setKeepInParent(keep);
633 }
634
635 public void onDensityOrFontScaleChanged() {
636 if (row != null) row.onDensityOrFontScaleChanged();
637 }
638
639 public boolean areGutsExposed() {
640 return row != null && row.getGuts() != null && row.getGuts().isExposed();
641 }
642
643 public boolean isChildInGroup() {
644 return parent == null;
645 }
646
Ned Burnsf81c4c42019-01-07 14:10:43 -0500647 /**
648 * @return Can the underlying notification be cleared? This can be different from whether the
649 * notification can be dismissed in case notifications are sensitive on the lockscreen.
650 * @see #canViewBeDismissed()
651 */
652 public boolean isClearable() {
653 if (notification == null || !notification.isClearable()) {
654 return false;
655 }
656 if (children.size() > 0) {
657 for (int i = 0; i < children.size(); i++) {
658 NotificationEntry child = children.get(i);
659 if (!child.isClearable()) {
660 return false;
661 }
662 }
663 }
664 return true;
665 }
666
667 public boolean canViewBeDismissed() {
668 if (row == null) return true;
669 return row.canViewBeDismissed();
670 }
671
672 @VisibleForTesting
673 boolean isExemptFromDndVisualSuppression() {
674 if (isNotificationBlockedByPolicy(notification.getNotification())) {
675 return false;
676 }
677
678 if ((notification.getNotification().flags
679 & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
680 return true;
681 }
682 if (notification.getNotification().isMediaNotification()) {
683 return true;
684 }
685 if (mIsSystemNotification != null && mIsSystemNotification) {
686 return true;
687 }
688 return false;
689 }
690
691 private boolean shouldSuppressVisualEffect(int effect) {
692 if (isExemptFromDndVisualSuppression()) {
693 return false;
694 }
695 return (suppressedVisualEffects & effect) != 0;
696 }
697
698 /**
699 * Returns whether {@link Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
700 * is set for this entry.
701 */
702 public boolean shouldSuppressFullScreenIntent() {
703 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
704 }
705
706 /**
707 * Returns whether {@link Policy#SUPPRESSED_EFFECT_PEEK}
708 * is set for this entry.
709 */
710 public boolean shouldSuppressPeek() {
711 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
712 }
713
714 /**
715 * Returns whether {@link Policy#SUPPRESSED_EFFECT_STATUS_BAR}
716 * is set for this entry.
717 */
718 public boolean shouldSuppressStatusBar() {
719 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
720 }
721
722 /**
723 * Returns whether {@link Policy#SUPPRESSED_EFFECT_AMBIENT}
724 * is set for this entry.
725 */
726 public boolean shouldSuppressAmbient() {
727 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
728 }
729
730 /**
731 * Returns whether {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
732 * is set for this entry.
733 */
734 public boolean shouldSuppressNotificationList() {
735 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
736 }
737
738 /**
739 * Categories that are explicitly called out on DND settings screens are always blocked, if
740 * DND has flagged them, even if they are foreground or system notifications that might
741 * otherwise visually bypass DND.
742 */
743 private static boolean isNotificationBlockedByPolicy(Notification n) {
744 return isCategory(CATEGORY_CALL, n)
745 || isCategory(CATEGORY_MESSAGE, n)
746 || isCategory(CATEGORY_ALARM, n)
747 || isCategory(CATEGORY_EVENT, n)
748 || isCategory(CATEGORY_REMINDER, n);
749 }
750
751 private static boolean isCategory(String category, Notification n) {
752 return Objects.equals(n.category, category);
753 }
Milo Sredkov13d88112019-02-01 12:23:24 +0000754
755 /** Information about a suggestion that is being edited. */
756 public static class EditedSuggestionInfo {
757
758 /**
759 * The value of the suggestion (before any user edits).
760 */
761 public final CharSequence originalText;
762
763 /**
764 * The index of the suggestion that is being edited.
765 */
766 public final int index;
767
768 public EditedSuggestionInfo(CharSequence originalText, int index) {
769 this.originalText = originalText;
770 this.index = index;
771 }
772 }
Ned Burnsf81c4c42019-01-07 14:10:43 -0500773}