blob: 33771449abc9a15e2c510ca8e0590effeee0fde0 [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;
Evan Laird04373662020-01-24 17:37:39 -050025import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
Ned Burnsf81c4c42019-01-07 14:10:43 -050026import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
Mady Mellordf48d0a2019-06-25 18:26:46 -070027import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
Ned Burnsf81c4c42019-01-07 14:10:43 -050028import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
29import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
30import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
31import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
32
Ned Burns4258d702020-01-08 19:58:57 -050033import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
Steve Elliott981cca12020-05-14 11:59:49 -040034import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_ALERTING;
Evan Laird25f02752019-08-14 19:25:06 -040035
Ned Burns4258d702020-01-08 19:58:57 -050036import static java.util.Objects.requireNonNull;
37
Evan Lairdccf5c5e2020-02-06 14:01:28 -050038import android.annotation.CurrentTimeMillisLong;
Ned Burnsf81c4c42019-01-07 14:10:43 -050039import android.app.Notification;
Julia Reynolds5cb8d5a2019-09-03 11:46:31 -040040import android.app.Notification.MessagingStyle.Message;
Ned Burnsf81c4c42019-01-07 14:10:43 -050041import android.app.NotificationChannel;
42import android.app.NotificationManager.Policy;
43import android.app.Person;
Steve Elliott6771f542020-05-26 13:31:10 -040044import android.app.RemoteInput;
Aran Ink2e3cc412019-12-13 16:30:17 -050045import android.app.RemoteInputHistoryItem;
Ned Burnsf81c4c42019-01-07 14:10:43 -050046import android.content.Context;
Alex Mangac2e48e2020-02-10 16:11:13 -080047import android.content.pm.ShortcutInfo;
Aran Ink2e3cc412019-12-13 16:30:17 -050048import android.net.Uri;
Ned Burnsf81c4c42019-01-07 14:10:43 -050049import android.os.Bundle;
Ned Burnsf81c4c42019-01-07 14:10:43 -050050import android.os.SystemClock;
Ned Burnsae90a9e2019-08-26 17:11:26 -040051import android.service.notification.NotificationListenerService.Ranking;
Ned Burnsf81c4c42019-01-07 14:10:43 -050052import android.service.notification.SnoozeCriterion;
53import android.service.notification.StatusBarNotification;
54import android.util.ArraySet;
Ned Burnsf81c4c42019-01-07 14:10:43 -050055
Ned Burns4258d702020-01-08 19:58:57 -050056import androidx.annotation.NonNull;
Ned Burnsf81c4c42019-01-07 14:10:43 -050057import androidx.annotation.Nullable;
58
59import com.android.internal.annotations.VisibleForTesting;
Ned Burnsf81c4c42019-01-07 14:10:43 -050060import com.android.internal.util.ArrayUtils;
61import com.android.internal.util.ContrastColorUtil;
62import com.android.systemui.statusbar.InflationTask;
Ned Burns4258d702020-01-08 19:58:57 -050063import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
Ned Burns77050aa2019-10-17 21:55:24 -040064import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
65import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
Beverlya53fb0d2020-01-29 15:26:13 -050066import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
Ned Burns012048d2020-01-08 19:57:30 -050067import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
Ned Burnsd8b51542020-03-13 20:52:43 -040068import com.android.systemui.statusbar.notification.icon.IconPack;
Ned Burnsf81c4c42019-01-07 14:10:43 -050069import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Dave Mankoffc0211ff2020-02-07 15:36:12 -050070import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
Ned Burnsf81c4c42019-01-07 14:10:43 -050071import com.android.systemui.statusbar.notification.row.NotificationGuts;
Steve Elliott981cca12020-05-14 11:59:49 -040072import com.android.systemui.statusbar.notification.stack.PriorityBucket;
Steve Elliott6771f542020-05-26 13:31:10 -040073import com.android.systemui.statusbar.phone.NotificationGroupManager;
Ned Burnsf81c4c42019-01-07 14:10:43 -050074
75import java.util.ArrayList;
Ned Burnsf81c4c42019-01-07 14:10:43 -050076import java.util.List;
77import java.util.Objects;
78
79/**
80 * Represents a notification that the system UI knows about
81 *
82 * Whenever the NotificationManager tells us about the existence of a new notification, we wrap it
83 * in a NotificationEntry. Thus, every notification has an associated NotificationEntry, even if
84 * that notification is never displayed to the user (for example, if it's filtered out for some
85 * reason).
86 *
87 * Entries store information about the current state of the notification. Essentially:
88 * anything that needs to persist or be modifiable even when the notification's views don't
89 * exist. Any other state should be stored on the views/view controllers themselves.
90 *
91 * At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
92 * clean this up in the future.
93 */
Ned Burns77050aa2019-10-17 21:55:24 -040094public final class NotificationEntry extends ListEntry {
Ned Burns47c98f12019-09-06 17:12:07 -040095
Ned Burns00b4b2d2019-10-17 22:09:27 -040096 private final String mKey;
97 private StatusBarNotification mSbn;
Ned Burnsae90a9e2019-08-26 17:11:26 -040098 private Ranking mRanking;
Evan Lairdccf5c5e2020-02-06 14:01:28 -050099 private long mCreationTime;
Ned Burnsae90a9e2019-08-26 17:11:26 -0400100
Ned Burnsf098dbf2019-09-13 19:17:53 -0400101 /*
102 * Bookkeeping members
103 */
104
105 /** List of lifetime extenders that are extending the lifetime of this notification. */
106 final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
107
Beverlya53fb0d2020-01-29 15:26:13 -0500108 /** List of dismiss interceptors that are intercepting the dismissal of this notification. */
109 final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
110
Ned Burns4258d702020-01-08 19:58:57 -0500111 /**
112 * If this notification was cancelled by system server, then the reason that was supplied.
113 * Uncancelled notifications always have REASON_NOT_CANCELED. Note that lifetime-extended
114 * notifications will have this set even though they are still in the active notification set.
115 */
116 @CancellationReason int mCancellationReason = REASON_NOT_CANCELED;
117
Ned Burns4258d702020-01-08 19:58:57 -0500118 /** @see #getDismissState() */
119 @NonNull private DismissState mDismissState = DismissState.NOT_DISMISSED;
Ned Burnsf098dbf2019-09-13 19:17:53 -0400120
121 /*
122 * Old members
123 * TODO: Remove every member beneath this line if possible
124 */
125
Ned Burnsd8b51542020-03-13 20:52:43 -0400126 private IconPack mIcons = IconPack.buildEmptyPack(null);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500127 private boolean interruption;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500128 public int targetSdk;
129 private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
130 public CharSequence remoteInputText;
Aran Ink2e3cc412019-12-13 16:30:17 -0500131 public String remoteInputMimeType;
132 public Uri remoteInputUri;
Mady Mellor7f234902019-10-20 12:06:29 -0700133 private Notification.BubbleMetadata mBubbleMetadata;
Julia Reynolds138111f2020-02-26 11:17:39 -0500134 private ShortcutInfo mShortcutInfo;
Milo Sredkov13d88112019-02-01 12:23:24 +0000135
136 /**
Steve Elliott6771f542020-05-26 13:31:10 -0400137 * If {@link RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
Milo Sredkov13d88112019-02-01 12:23:24 +0000138 * currently editing a choice (smart reply), then this field contains the information about the
139 * suggestion being edited. Otherwise <code>null</code>.
140 */
141 public EditedSuggestionInfo editedSuggestionInfo;
142
Ned Burnsf81c4c42019-01-07 14:10:43 -0500143 private ExpandableNotificationRow row; // the outer expanded view
Dave Mankoffc0211ff2020-02-07 15:36:12 -0500144 private ExpandableNotificationRowController mRowController;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500145
146 private int mCachedContrastColor = COLOR_INVALID;
147 private int mCachedContrastColorIsFor = COLOR_INVALID;
148 private InflationTask mRunningTask = null;
149 private Throwable mDebugThrowable;
150 public CharSequence remoteInputTextWhenReset;
151 public long lastRemoteInputSent = NOT_LAUNCHED_YET;
Beverly201cdd52019-10-18 14:30:46 -0400152 public final ArraySet<Integer> mActiveAppOps = new ArraySet<>(3);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500153 public CharSequence headsUpStatusBarText;
154 public CharSequence headsUpStatusBarTextPublic;
155
Beverly95fdfad2020-05-15 10:15:42 -0400156 // indicates when this entry's view was first attached to a window
157 // this value will reset when the view is completely removed from the shade (ie: filtered out)
Ned Burnsf81c4c42019-01-07 14:10:43 -0500158 private long initializationTime = -1;
159
160 /**
161 * Whether or not this row represents a system notification. Note that if this is
162 * {@code null}, that means we were either unable to retrieve the info or have yet to
163 * retrieve the info.
164 */
165 public Boolean mIsSystemNotification;
166
167 /**
168 * Has the user sent a reply through this Notification.
169 */
170 private boolean hasSentReply;
171
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700172 private boolean mSensitive = true;
Ned Burnsd8b51542020-03-13 20:52:43 -0400173 private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>();
174
Selim Cineke3c6e462019-06-24 19:37:06 -0700175 private boolean mAutoHeadsUp;
Selim Cinek65c96f22019-07-25 20:09:04 -0700176 private boolean mPulseSupressed;
Evan Laird04373662020-01-24 17:37:39 -0500177 private boolean mAllowFgsDismissal;
Evan Laird25f02752019-08-14 19:25:06 -0400178 private int mBucket = BUCKET_ALERTING;
Steve Elliott6771f542020-05-26 13:31:10 -0400179 @Nullable private Long mPendingAnimationDuration;
180 private boolean mIsMarkedForUserTriggeredMovement;
Gus Prevascaed15c2019-01-18 14:19:51 -0500181
Evan Lairdccf5c5e2020-02-06 14:01:28 -0500182 /**
183 * @param sbn the StatusBarNotification from system server
184 * @param ranking also from system server
185 * @param creationTime SystemClock.uptimeMillis of when we were created
186 */
Ned Burnsae90a9e2019-08-26 17:11:26 -0400187 public NotificationEntry(
Ned Burns636c3792019-09-11 16:59:07 -0400188 @NonNull StatusBarNotification sbn,
Evan Lairdccf5c5e2020-02-06 14:01:28 -0500189 @NonNull Ranking ranking,
190 long creationTime) {
191 this(sbn, ranking, false, creationTime);
Evan Laird04373662020-01-24 17:37:39 -0500192 }
193
194 public NotificationEntry(
195 @NonNull StatusBarNotification sbn,
196 @NonNull Ranking ranking,
Evan Lairdccf5c5e2020-02-06 14:01:28 -0500197 boolean allowFgsDismissal,
198 long creationTime
Evan Laird04373662020-01-24 17:37:39 -0500199 ) {
Steve Elliott6771f542020-05-26 13:31:10 -0400200 super(requireNonNull(requireNonNull(sbn).getKey()));
Ned Burns77050aa2019-10-17 21:55:24 -0400201
Ned Burns4258d702020-01-08 19:58:57 -0500202 requireNonNull(ranking);
Ned Burns00b4b2d2019-10-17 22:09:27 -0400203
Evan Lairdccf5c5e2020-02-06 14:01:28 -0500204 mCreationTime = creationTime;
Ned Burns00b4b2d2019-10-17 22:09:27 -0400205 mKey = sbn.getKey();
206 setSbn(sbn);
Ned Burns636c3792019-09-11 16:59:07 -0400207 setRanking(ranking);
Evan Laird04373662020-01-24 17:37:39 -0500208
209 mAllowFgsDismissal = allowFgsDismissal;
Ned Burnsae90a9e2019-08-26 17:11:26 -0400210 }
211
Ned Burns77050aa2019-10-17 21:55:24 -0400212 @Override
213 public NotificationEntry getRepresentativeEntry() {
214 return this;
215 }
216
Ned Burnsae90a9e2019-08-26 17:11:26 -0400217 /** The key for this notification. Guaranteed to be immutable and unique */
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700218 @NonNull public String getKey() {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400219 return mKey;
Ned Burnsae90a9e2019-08-26 17:11:26 -0400220 }
221
222 /**
223 * The StatusBarNotification that represents one half of a NotificationEntry (the other half
224 * being the Ranking). This object is swapped out whenever a notification is updated.
225 */
Selim Cinek5dbef2d2020-05-07 17:44:38 -0700226 @NonNull public StatusBarNotification getSbn() {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400227 return mSbn;
Ned Burnsae90a9e2019-08-26 17:11:26 -0400228 }
229
230 /**
231 * Should only be called by NotificationEntryManager and friends.
232 * TODO: Make this package-private
233 */
Ned Burns00b4b2d2019-10-17 22:09:27 -0400234 public void setSbn(@NonNull StatusBarNotification sbn) {
Ned Burns4258d702020-01-08 19:58:57 -0500235 requireNonNull(sbn);
236 requireNonNull(sbn.getKey());
Ned Burns00b4b2d2019-10-17 22:09:27 -0400237
238 if (!sbn.getKey().equals(mKey)) {
Ned Burnsae90a9e2019-08-26 17:11:26 -0400239 throw new IllegalArgumentException("New key " + sbn.getKey()
Ned Burns00b4b2d2019-10-17 22:09:27 -0400240 + " doesn't match existing key " + mKey);
Ned Burnsae90a9e2019-08-26 17:11:26 -0400241 }
Ned Burns00b4b2d2019-10-17 22:09:27 -0400242
Beverlye558f1d2020-01-07 16:28:58 -0500243 mSbn = sbn;
244 mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
Ned Burnsae90a9e2019-08-26 17:11:26 -0400245 }
246
247 /**
248 * The Ranking that represents one half of a NotificationEntry (the other half being the
249 * StatusBarNotification). This object is swapped out whenever a the ranking is updated (which
250 * generally occurs whenever anything changes in the notification list).
251 */
Evan Laird9afe7662019-10-16 17:16:39 -0400252 public Ranking getRanking() {
Ned Burnsae90a9e2019-08-26 17:11:26 -0400253 return mRanking;
254 }
255
256 /**
Evan Lairdccf5c5e2020-02-06 14:01:28 -0500257 * A timestamp of SystemClock.uptimeMillis() of when this entry was first created, regardless
258 * of any changes to the data presented. It is set once on creation and will never change, and
259 * allows us to know exactly how long this notification has been alive for in our listener
260 * service. It is entirely unrelated to the information inside of the notification.
261 *
262 * This is different to Notification#when because it persists throughout updates, whereas
263 * system server treats every single call to notify() as a new notification and we handle
264 * updates to NotificationEntry locally.
265 */
266 @CurrentTimeMillisLong
267 public long getCreationTime() {
268 return mCreationTime;
269 }
270
271 /**
Ned Burnsae90a9e2019-08-26 17:11:26 -0400272 * Should only be called by NotificationEntryManager and friends.
273 * TODO: Make this package-private
274 */
275 public void setRanking(@NonNull Ranking ranking) {
Ned Burns4258d702020-01-08 19:58:57 -0500276 requireNonNull(ranking);
277 requireNonNull(ranking.getKey());
Ned Burns00b4b2d2019-10-17 22:09:27 -0400278
279 if (!ranking.getKey().equals(mKey)) {
Ned Burns636c3792019-09-11 16:59:07 -0400280 throw new IllegalArgumentException("New key " + ranking.getKey()
Ned Burns00b4b2d2019-10-17 22:09:27 -0400281 + " doesn't match existing key " + mKey);
Ned Burns636c3792019-09-11 16:59:07 -0400282 }
Ned Burns62644722019-10-18 13:13:00 -0400283
Beverlye558f1d2020-01-07 16:28:58 -0500284 mRanking = ranking;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500285 }
286
Ned Burns62644722019-10-18 13:13:00 -0400287 /*
Ned Burns4258d702020-01-08 19:58:57 -0500288 * Bookkeeping getters and setters
289 */
290
291 /**
Ned Burns4258d702020-01-08 19:58:57 -0500292 * Set if the user has dismissed this notif but we haven't yet heard back from system server to
293 * confirm the dismissal.
294 */
295 @NonNull public DismissState getDismissState() {
296 return mDismissState;
297 }
298
299 void setDismissState(@NonNull DismissState dismissState) {
300 mDismissState = requireNonNull(dismissState);
301 }
302
Ned Burnsd97aab02020-03-30 23:08:39 -0400303 @Nullable public NotifFilter getExcludingFilter() {
304 return getAttachState().getExcludingFilter();
305 }
306
307 @Nullable public NotifPromoter getNotifPromoter() {
308 return getAttachState().getPromoter();
309 }
310
Ned Burns4258d702020-01-08 19:58:57 -0500311 /*
Ned Burns62644722019-10-18 13:13:00 -0400312 * Convenience getters for SBN and Ranking members
313 */
314
Ned Burns296aec162019-09-04 17:30:59 -0400315 public NotificationChannel getChannel() {
316 return mRanking.getChannel();
317 }
318
Ned Burns60e94592019-09-06 14:47:25 -0400319 public long getLastAudiblyAlertedMs() {
320 return mRanking.getLastAudiblyAlertedMillis();
321 }
322
323 public boolean isAmbient() {
324 return mRanking.isAmbient();
325 }
326
327 public int getImportance() {
328 return mRanking.getImportance();
329 }
330
Ned Burns47c98f12019-09-06 17:12:07 -0400331 public List<SnoozeCriterion> getSnoozeCriteria() {
332 return mRanking.getSnoozeCriteria();
333 }
334
335 public int getUserSentiment() {
336 return mRanking.getUserSentiment();
337 }
338
339 public int getSuppressedVisualEffects() {
340 return mRanking.getSuppressedVisualEffects();
341 }
342
Ned Burns47c98f12019-09-06 17:12:07 -0400343 /** @see Ranking#canBubble() */
344 public boolean canBubble() {
345 return mRanking.canBubble();
346 }
347
Ned Burns5a9e35202019-09-06 22:26:33 -0400348 public @NonNull List<Notification.Action> getSmartActions() {
349 return mRanking.getSmartActions();
350 }
351
352 public @NonNull List<CharSequence> getSmartReplies() {
353 return mRanking.getSmartReplies();
354 }
355
Ned Burns47c98f12019-09-06 17:12:07 -0400356
Ned Burns62644722019-10-18 13:13:00 -0400357 /*
358 * Old methods
359 *
360 * TODO: Remove as many of these as possible
361 */
362
Ned Burnsd8b51542020-03-13 20:52:43 -0400363 @NonNull
364 public IconPack getIcons() {
365 return mIcons;
366 }
367
368 public void setIcons(@NonNull IconPack icons) {
369 mIcons = icons;
370 }
371
Ned Burnsf81c4c42019-01-07 14:10:43 -0500372 public void setInterruption() {
373 interruption = true;
374 }
375
376 public boolean hasInterrupted() {
377 return interruption;
378 }
379
Ned Burnsf81c4c42019-01-07 14:10:43 -0500380 public boolean isBubble() {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400381 return (mSbn.getNotification().flags & FLAG_BUBBLE) != 0;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500382 }
383
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800384 /**
Mady Mellor9801e852019-01-22 14:50:28 -0800385 * Returns the data needed for a bubble for this notification, if it exists.
386 */
Evan Laird03c44452020-04-16 12:26:07 -0400387 @Nullable
Mady Mellor9801e852019-01-22 14:50:28 -0800388 public Notification.BubbleMetadata getBubbleMetadata() {
Mady Mellor7f234902019-10-20 12:06:29 -0700389 return mBubbleMetadata;
390 }
391
392 /**
393 * Sets bubble metadata for this notification.
394 */
Evan Laird03c44452020-04-16 12:26:07 -0400395 public void setBubbleMetadata(@Nullable Notification.BubbleMetadata metadata) {
Mady Mellor7f234902019-10-20 12:06:29 -0700396 mBubbleMetadata = metadata;
397 }
398
399 /**
400 * Updates the {@link Notification#FLAG_BUBBLE} flag on this notification to indicate
401 * whether it is a bubble or not. If this entry is set to not bubble, or does not have
402 * the required info to bubble, the flag cannot be set to true.
403 *
404 * @param shouldBubble whether this notification should be flagged as a bubble.
405 * @return true if the value changed.
406 */
407 public boolean setFlagBubble(boolean shouldBubble) {
408 boolean wasBubble = isBubble();
409 if (!shouldBubble) {
410 mSbn.getNotification().flags &= ~FLAG_BUBBLE;
411 } else if (mBubbleMetadata != null && canBubble()) {
412 // wants to be bubble & can bubble, set flag
413 mSbn.getNotification().flags |= FLAG_BUBBLE;
414 }
415 return wasBubble != isBubble();
Mady Mellor9801e852019-01-22 14:50:28 -0800416 }
417
Steve Elliott981cca12020-05-14 11:59:49 -0400418 @PriorityBucket
Evan Laird25f02752019-08-14 19:25:06 -0400419 public int getBucket() {
420 return mBucket;
421 }
422
Steve Elliott981cca12020-05-14 11:59:49 -0400423 public void setBucket(@PriorityBucket int bucket) {
Evan Laird25f02752019-08-14 19:25:06 -0400424 mBucket = bucket;
425 }
426
Ned Burnsf81c4c42019-01-07 14:10:43 -0500427 public ExpandableNotificationRow getRow() {
428 return row;
429 }
430
431 //TODO: This will go away when we have a way to bind an entry to a row
432 public void setRow(ExpandableNotificationRow row) {
433 this.row = row;
434 }
435
Dave Mankoffc0211ff2020-02-07 15:36:12 -0500436 public ExpandableNotificationRowController getRowController() {
437 return mRowController;
438 }
439
440 public void setRowController(ExpandableNotificationRowController controller) {
441 mRowController = controller;
442 }
443
Kevin Han43077f92020-02-28 12:51:53 -0800444 /**
445 * Get the children that are actually attached to this notification's row.
446 *
447 * TODO: Seems like most callers here should probably be using
Steve Elliott6771f542020-05-26 13:31:10 -0400448 * {@link NotificationGroupManager#getChildren}
Kevin Han43077f92020-02-28 12:51:53 -0800449 */
450 public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
Evan Lairdce2d1af2019-05-30 16:00:22 -0400451 if (row == null) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500452 return null;
453 }
454
Kevin Han43077f92020-02-28 12:51:53 -0800455 List<ExpandableNotificationRow> rowChildren = row.getAttachedChildren();
Evan Lairdce2d1af2019-05-30 16:00:22 -0400456 if (rowChildren == null) {
457 return null;
458 }
459
460 ArrayList<NotificationEntry> children = new ArrayList<>();
461 for (ExpandableNotificationRow child : rowChildren) {
462 children.add(child.getEntry());
463 }
464
Ned Burnsf81c4c42019-01-07 14:10:43 -0500465 return children;
466 }
467
468 public void notifyFullScreenIntentLaunched() {
469 setInterruption();
470 lastFullScreenIntentLaunchTime = SystemClock.elapsedRealtime();
471 }
472
473 public boolean hasJustLaunchedFullScreenIntent() {
474 return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
475 }
476
477 public boolean hasJustSentRemoteInput() {
478 return SystemClock.elapsedRealtime() < lastRemoteInputSent + REMOTE_INPUT_COOLDOWN;
479 }
480
481 public boolean hasFinishedInitialization() {
Beverly95fdfad2020-05-15 10:15:42 -0400482 return initializationTime != -1
483 && SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500484 }
485
Ned Burnsf81c4c42019-01-07 14:10:43 -0500486 public int getContrastedColor(Context context, boolean isLowPriority,
487 int backgroundColor) {
488 int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
Ned Burns00b4b2d2019-10-17 22:09:27 -0400489 mSbn.getNotification().color;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500490 if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
491 return mCachedContrastColor;
492 }
493 final int contrasted = ContrastColorUtil.resolveContrastColor(context, rawColor,
494 backgroundColor);
495 mCachedContrastColorIsFor = rawColor;
496 mCachedContrastColor = contrasted;
497 return mCachedContrastColor;
498 }
499
500 /**
501 * Abort all existing inflation tasks
502 */
503 public void abortTask() {
504 if (mRunningTask != null) {
505 mRunningTask.abort();
506 mRunningTask = null;
507 }
508 }
509
510 public void setInflationTask(InflationTask abortableTask) {
511 // abort any existing inflation
Ned Burnsf81c4c42019-01-07 14:10:43 -0500512 abortTask();
513 mRunningTask = abortableTask;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500514 }
515
516 public void onInflationTaskFinished() {
517 mRunningTask = null;
518 }
519
520 @VisibleForTesting
521 public InflationTask getRunningTask() {
522 return mRunningTask;
523 }
524
525 /**
526 * Set a throwable that is used for debugging
527 *
528 * @param debugThrowable the throwable to save
529 */
530 public void setDebugThrowable(Throwable debugThrowable) {
531 mDebugThrowable = debugThrowable;
532 }
533
534 public Throwable getDebugThrowable() {
535 return mDebugThrowable;
536 }
537
538 public void onRemoteInputInserted() {
539 lastRemoteInputSent = NOT_LAUNCHED_YET;
540 remoteInputTextWhenReset = null;
541 }
542
543 public void setHasSentReply() {
544 hasSentReply = true;
545 }
546
547 public boolean isLastMessageFromReply() {
548 if (!hasSentReply) {
549 return false;
550 }
Ned Burns00b4b2d2019-10-17 22:09:27 -0400551 Bundle extras = mSbn.getNotification().extras;
Aran Ink2e3cc412019-12-13 16:30:17 -0500552 RemoteInputHistoryItem[] replyTexts = (RemoteInputHistoryItem[]) extras.getParcelableArray(
553 Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500554 if (!ArrayUtils.isEmpty(replyTexts)) {
555 return true;
556 }
Julia Reynolds5cb8d5a2019-09-03 11:46:31 -0400557 List<Message> messages = Message.getMessagesFromBundleArray(
558 extras.getParcelableArray(Notification.EXTRA_MESSAGES));
559 if (messages != null && !messages.isEmpty()) {
560 Message lastMessage = messages.get(messages.size() -1);
561
562 if (lastMessage != null) {
563 Person senderPerson = lastMessage.getSenderPerson();
564 if (senderPerson == null) {
565 return true;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500566 }
Julia Reynolds5cb8d5a2019-09-03 11:46:31 -0400567 Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
568 return Objects.equals(user, senderPerson);
Ned Burnsf81c4c42019-01-07 14:10:43 -0500569 }
570 }
571 return false;
572 }
573
Beverly95fdfad2020-05-15 10:15:42 -0400574 public void resetInitializationTime() {
575 initializationTime = -1;
576 }
577
Ned Burnsf81c4c42019-01-07 14:10:43 -0500578 public void setInitializationTime(long time) {
579 if (initializationTime == -1) {
580 initializationTime = time;
581 }
582 }
583
584 public void sendAccessibilityEvent(int eventType) {
585 if (row != null) {
586 row.sendAccessibilityEvent(eventType);
587 }
588 }
589
590 /**
591 * Used by NotificationMediaManager to determine... things
592 * @return {@code true} if we are a media notification
593 */
594 public boolean isMediaNotification() {
595 if (row == null) return false;
596
597 return row.isMediaRow();
598 }
599
600 /**
601 * We are a top level child if our parent is the list of notifications duh
602 * @return {@code true} if we're a top level notification
603 */
604 public boolean isTopLevelChild() {
605 return row != null && row.isTopLevelChild();
606 }
607
608 public void resetUserExpansion() {
609 if (row != null) row.resetUserExpansion();
610 }
611
Ned Burnsf81c4c42019-01-07 14:10:43 -0500612 public boolean rowExists() {
613 return row != null;
614 }
615
616 public boolean isRowDismissed() {
617 return row != null && row.isDismissed();
618 }
619
620 public boolean isRowRemoved() {
621 return row != null && row.isRemoved();
622 }
623
624 /**
625 * @return {@code true} if the row is null or removed
626 */
627 public boolean isRemoved() {
628 //TODO: recycling invalidates this
629 return row == null || row.isRemoved();
630 }
631
Ned Burnsf81c4c42019-01-07 14:10:43 -0500632 public boolean isRowPinned() {
633 return row != null && row.isPinned();
634 }
635
Selim Cinek94d0be82020-04-06 19:20:47 -0700636 /**
637 * Is this entry pinned and was expanded while doing so
638 */
639 public boolean isPinnedAndExpanded() {
640 return row != null && row.isPinnedAndExpanded();
641 }
642
Ned Burnsf81c4c42019-01-07 14:10:43 -0500643 public void setRowPinned(boolean pinned) {
644 if (row != null) row.setPinned(pinned);
645 }
646
Ned Burnsf81c4c42019-01-07 14:10:43 -0500647 public boolean isRowHeadsUp() {
648 return row != null && row.isHeadsUp();
649 }
650
Selim Cinekb57dd8a2019-06-14 12:27:58 -0700651 public boolean showingPulsing() {
652 return row != null && row.showingPulsing();
653 }
654
Ned Burnsf81c4c42019-01-07 14:10:43 -0500655 public void setHeadsUp(boolean shouldHeadsUp) {
656 if (row != null) row.setHeadsUp(shouldHeadsUp);
657 }
658
Selim Cinekc3fec682019-06-06 18:11:07 -0700659 public void setHeadsUpAnimatingAway(boolean animatingAway) {
660 if (row != null) row.setHeadsUpAnimatingAway(animatingAway);
Selim Cinek459aee32019-02-20 11:18:56 -0800661 }
662
Selim Cineke3c6e462019-06-24 19:37:06 -0700663 /**
664 * Set that this notification was automatically heads upped. This happens for example when
665 * the user bypasses the lockscreen and media is playing.
666 */
667 public void setAutoHeadsUp(boolean autoHeadsUp) {
668 mAutoHeadsUp = autoHeadsUp;
669 }
670
671 /**
672 * @return if this notification was automatically heads upped. This happens for example when
673 * * the user bypasses the lockscreen and media is playing.
674 */
675 public boolean isAutoHeadsUp() {
676 return mAutoHeadsUp;
677 }
Selim Cinek459aee32019-02-20 11:18:56 -0800678
Ned Burnsf81c4c42019-01-07 14:10:43 -0500679 public boolean mustStayOnScreen() {
680 return row != null && row.mustStayOnScreen();
681 }
682
683 public void setHeadsUpIsVisible() {
684 if (row != null) row.setHeadsUpIsVisible();
685 }
686
687 //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
688 public ExpandableNotificationRow getHeadsUpAnimationView() {
689 return row;
690 }
691
692 public void setUserLocked(boolean userLocked) {
693 if (row != null) row.setUserLocked(userLocked);
694 }
695
696 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
697 if (row != null) row.setUserExpanded(userExpanded, allowChildExpansion);
698 }
699
700 public void setGroupExpansionChanging(boolean changing) {
701 if (row != null) row.setGroupExpansionChanging(changing);
702 }
703
704 public void notifyHeightChanged(boolean needsAnimation) {
705 if (row != null) row.notifyHeightChanged(needsAnimation);
706 }
707
708 public void closeRemoteInput() {
709 if (row != null) row.closeRemoteInput();
710 }
711
712 public boolean areChildrenExpanded() {
713 return row != null && row.areChildrenExpanded();
714 }
715
716 public boolean keepInParent() {
717 return row != null && row.keepInParent();
718 }
719
720 //TODO: probably less confusing to say "is group fully visible"
721 public boolean isGroupNotFullyVisible() {
722 return row == null || row.isGroupNotFullyVisible();
723 }
724
725 public NotificationGuts getGuts() {
726 if (row != null) return row.getGuts();
727 return null;
728 }
729
Ned Burnsf81c4c42019-01-07 14:10:43 -0500730 public void removeRow() {
731 if (row != null) row.setRemoved();
732 }
733
734 public boolean isSummaryWithChildren() {
735 return row != null && row.isSummaryWithChildren();
736 }
737
738 public void setKeepInParent(boolean keep) {
739 if (row != null) row.setKeepInParent(keep);
740 }
741
742 public void onDensityOrFontScaleChanged() {
743 if (row != null) row.onDensityOrFontScaleChanged();
744 }
745
746 public boolean areGutsExposed() {
747 return row != null && row.getGuts() != null && row.getGuts().isExposed();
748 }
749
750 public boolean isChildInGroup() {
Selim Cinekba069ae2020-04-01 19:45:16 -0700751 return row != null && row.isChildInGroup();
Ned Burnsf81c4c42019-01-07 14:10:43 -0500752 }
753
Ned Burnsf81c4c42019-01-07 14:10:43 -0500754 /**
755 * @return Can the underlying notification be cleared? This can be different from whether the
756 * notification can be dismissed in case notifications are sensitive on the lockscreen.
757 * @see #canViewBeDismissed()
758 */
Evan Laird04373662020-01-24 17:37:39 -0500759 // TOOD: This logic doesn't belong on NotificationEntry. It should be moved to the
760 // ForegroundsServiceDismissalFeatureController or some other controller that can be added
761 // as a dependency to any class that needs to answer this question.
Ned Burnsf81c4c42019-01-07 14:10:43 -0500762 public boolean isClearable() {
Evan Laird04373662020-01-24 17:37:39 -0500763 if (!isDismissable()) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500764 return false;
765 }
Evan Lairdce2d1af2019-05-30 16:00:22 -0400766
Kevin Han43077f92020-02-28 12:51:53 -0800767 List<NotificationEntry> children = getAttachedNotifChildren();
Evan Lairdce2d1af2019-05-30 16:00:22 -0400768 if (children != null && children.size() > 0) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500769 for (int i = 0; i < children.size(); i++) {
770 NotificationEntry child = children.get(i);
Evan Laird04373662020-01-24 17:37:39 -0500771 if (!child.isDismissable()) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500772 return false;
773 }
774 }
775 }
776 return true;
777 }
778
Evan Laird04373662020-01-24 17:37:39 -0500779 /**
780 * Notifications might have any combination of flags:
781 * - FLAG_ONGOING_EVENT
782 * - FLAG_NO_CLEAR
783 * - FLAG_FOREGROUND_SERVICE
784 *
785 * We want to allow dismissal of notifications that represent foreground services, which may
786 * have all 3 flags set. If we only find NO_CLEAR though, we don't want to allow dismissal
787 */
788 private boolean isDismissable() {
789 boolean ongoing = ((mSbn.getNotification().flags & Notification.FLAG_ONGOING_EVENT) != 0);
790 boolean noclear = ((mSbn.getNotification().flags & Notification.FLAG_NO_CLEAR) != 0);
791 boolean fgs = ((mSbn.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0);
792
793 if (mAllowFgsDismissal) {
794 if (noclear && !ongoing && !fgs) {
795 return false;
796 }
797 return true;
798 } else {
799 return mSbn.isClearable();
800 }
801
802 }
803
Ned Burnsf81c4c42019-01-07 14:10:43 -0500804 public boolean canViewBeDismissed() {
805 if (row == null) return true;
806 return row.canViewBeDismissed();
807 }
808
809 @VisibleForTesting
810 boolean isExemptFromDndVisualSuppression() {
Ned Burns00b4b2d2019-10-17 22:09:27 -0400811 if (isNotificationBlockedByPolicy(mSbn.getNotification())) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500812 return false;
813 }
814
Ned Burns00b4b2d2019-10-17 22:09:27 -0400815 if ((mSbn.getNotification().flags
Steve Elliott6771f542020-05-26 13:31:10 -0400816 & FLAG_FOREGROUND_SERVICE) != 0) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500817 return true;
818 }
Ned Burns00b4b2d2019-10-17 22:09:27 -0400819 if (mSbn.getNotification().isMediaNotification()) {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500820 return true;
821 }
822 if (mIsSystemNotification != null && mIsSystemNotification) {
823 return true;
824 }
825 return false;
826 }
827
828 private boolean shouldSuppressVisualEffect(int effect) {
829 if (isExemptFromDndVisualSuppression()) {
830 return false;
831 }
Ned Burns47c98f12019-09-06 17:12:07 -0400832 return (getSuppressedVisualEffects() & effect) != 0;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500833 }
834
835 /**
836 * Returns whether {@link Policy#SUPPRESSED_EFFECT_FULL_SCREEN_INTENT}
837 * is set for this entry.
838 */
839 public boolean shouldSuppressFullScreenIntent() {
840 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
841 }
842
843 /**
844 * Returns whether {@link Policy#SUPPRESSED_EFFECT_PEEK}
845 * is set for this entry.
846 */
847 public boolean shouldSuppressPeek() {
848 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_PEEK);
849 }
850
851 /**
852 * Returns whether {@link Policy#SUPPRESSED_EFFECT_STATUS_BAR}
853 * is set for this entry.
854 */
855 public boolean shouldSuppressStatusBar() {
856 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_STATUS_BAR);
857 }
858
859 /**
860 * Returns whether {@link Policy#SUPPRESSED_EFFECT_AMBIENT}
861 * is set for this entry.
862 */
863 public boolean shouldSuppressAmbient() {
864 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_AMBIENT);
865 }
866
867 /**
868 * Returns whether {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
869 * is set for this entry.
870 */
871 public boolean shouldSuppressNotificationList() {
872 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
873 }
874
Mady Mellordf48d0a2019-06-25 18:26:46 -0700875
876 /**
877 * Returns whether {@link Policy#SUPPRESSED_EFFECT_BADGE}
878 * is set for this entry. This badge is not an app badge, but rather an indicator of "unseen"
879 * content. Typically this is referred to as a "dot" internally in Launcher & SysUI code.
880 */
881 public boolean shouldSuppressNotificationDot() {
882 return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_BADGE);
883 }
884
Ned Burnsf81c4c42019-01-07 14:10:43 -0500885 /**
886 * Categories that are explicitly called out on DND settings screens are always blocked, if
887 * DND has flagged them, even if they are foreground or system notifications that might
888 * otherwise visually bypass DND.
889 */
890 private static boolean isNotificationBlockedByPolicy(Notification n) {
891 return isCategory(CATEGORY_CALL, n)
892 || isCategory(CATEGORY_MESSAGE, n)
893 || isCategory(CATEGORY_ALARM, n)
894 || isCategory(CATEGORY_EVENT, n)
895 || isCategory(CATEGORY_REMINDER, n);
896 }
897
898 private static boolean isCategory(String category, Notification n) {
899 return Objects.equals(n.category, category);
900 }
Milo Sredkov13d88112019-02-01 12:23:24 +0000901
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700902 /**
Beverly8c80e0c2019-12-03 11:25:39 -0500903 * Whether or not this row represents a system notification. Note that if this is
904 * {@code null}, that means we were either unable to retrieve the info or have yet to
905 * retrieve the info.
906 */
907 public Boolean isSystemNotification() {
908 return mIsSystemNotification;
909 }
910
911 /**
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700912 * Set this notification to be sensitive.
913 *
914 * @param sensitive true if the content of this notification is sensitive right now
915 * @param deviceSensitive true if the device in general is sensitive right now
916 */
917 public void setSensitive(boolean sensitive, boolean deviceSensitive) {
918 getRow().setSensitive(sensitive, deviceSensitive);
919 if (sensitive != mSensitive) {
920 mSensitive = sensitive;
Ned Burnsd8b51542020-03-13 20:52:43 -0400921 for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) {
922 mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this);
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700923 }
924 }
925 }
926
927 public boolean isSensitive() {
928 return mSensitive;
929 }
930
Ned Burnsd8b51542020-03-13 20:52:43 -0400931 /** Add a listener to be notified when the entry's sensitivity changes. */
932 public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
933 mOnSensitivityChangedListeners.add(listener);
934 }
935
936 /** Remove a listener that was registered above. */
937 public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
938 mOnSensitivityChangedListeners.remove(listener);
Selim Cinekb2c5dc52019-06-24 15:46:52 -0700939 }
940
Selim Cinek65c96f22019-07-25 20:09:04 -0700941 public boolean isPulseSuppressed() {
942 return mPulseSupressed;
943 }
944
945 public void setPulseSuppressed(boolean suppressed) {
946 mPulseSupressed = suppressed;
947 }
948
Steve Elliott6771f542020-05-26 13:31:10 -0400949 /** Whether or not this entry has been marked for a user-triggered movement. */
950 public boolean isMarkedForUserTriggeredMovement() {
951 return mIsMarkedForUserTriggeredMovement;
952 }
953
954 /**
955 * Mark this entry for movement triggered by a user action (ex: changing the priorirty of a
956 * conversation). This can then be used for custom animations.
957 */
958 public void markForUserTriggeredMovement(boolean marked) {
959 mIsMarkedForUserTriggeredMovement = marked;
960 }
961
Milo Sredkov13d88112019-02-01 12:23:24 +0000962 /** Information about a suggestion that is being edited. */
963 public static class EditedSuggestionInfo {
964
965 /**
966 * The value of the suggestion (before any user edits).
967 */
968 public final CharSequence originalText;
969
970 /**
971 * The index of the suggestion that is being edited.
972 */
973 public final int index;
974
975 public EditedSuggestionInfo(CharSequence originalText, int index) {
976 this.originalText = originalText;
977 this.index = index;
978 }
979 }
Ned Burns9ebf3822019-10-18 13:31:20 -0400980
Ned Burnsd8b51542020-03-13 20:52:43 -0400981 /** Listener interface for {@link #addOnSensitivityChangedListener} */
982 public interface OnSensitivityChangedListener {
983 /** Called when the sensitivity changes */
984 void onSensitivityChanged(@NonNull NotificationEntry entry);
985 }
986
Ned Burns4258d702020-01-08 19:58:57 -0500987 /** @see #getDismissState() */
988 public enum DismissState {
989 /** User has not dismissed this notif or its parent */
990 NOT_DISMISSED,
991 /** User has dismissed this notif specifically */
992 DISMISSED,
993 /** User has dismissed this notif's parent (which implicitly dismisses this one as well) */
994 PARENT_DISMISSED,
995 }
996
Ned Burns9ebf3822019-10-18 13:31:20 -0400997 private static final long LAUNCH_COOLDOWN = 2000;
998 private static final long REMOTE_INPUT_COOLDOWN = 500;
999 private static final long INITIALIZATION_DELAY = 400;
1000 private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
1001 private static final int COLOR_INVALID = 1;
Ned Burnsf81c4c42019-01-07 14:10:43 -05001002}