1 /* 2 * Copyright (C) 2018 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 package com.android.car.notification; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.app.Notification; 21 import android.os.Build; 22 import android.os.Bundle; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 27 /** 28 * Data structure representing a notification card in car. 29 * A notification group can hold either: 30 * <ol> 31 * <li>One notification with no group summary notification</li> 32 * <li>One group summary notification with no child notifications</li> 33 * <li>A group of notifications with a group summary notification</li> 34 * </ol> 35 */ 36 public class NotificationGroup { 37 private static final String TAG = "NotificationGroup"; 38 private static final boolean DEBUG = Build.IS_DEBUGGABLE; 39 40 private final List<AlertEntry> mNotifications = new ArrayList<>(); 41 42 @Nullable 43 private List<String> mChildTitles; 44 @Nullable 45 private AlertEntry mGroupSummaryNotification; 46 private String mGroupKey; 47 private boolean mIsHeader; 48 private boolean mIsFooter; 49 private boolean mIsRecentsHeader; 50 private boolean mIsOlderHeader; 51 private boolean mIsSeen; 52 NotificationGroup()53 public NotificationGroup() { 54 } 55 NotificationGroup(AlertEntry alertEntry)56 public NotificationGroup(AlertEntry alertEntry) { 57 addNotification(alertEntry); 58 } 59 NotificationGroup(NotificationGroup group)60 public NotificationGroup(NotificationGroup group) { 61 setGroupKey(group.getGroupKey()); 62 if (group.getGroupSummaryNotification() != null) { 63 setGroupSummaryNotification(group.getGroupSummaryNotification()); 64 } 65 for (AlertEntry alertEntry : group.getChildNotifications()) { 66 addNotification(alertEntry); 67 } 68 setChildTitles(group.getChildTitles()); 69 setFooter(group.isFooter()); 70 setHeader(group.isHeader()); 71 setOlderHeader(group.isOlderHeader()); 72 setRecentsHeader(group.isRecentsHeader()); 73 setSeen(group.isSeen()); 74 } 75 76 /** 77 * Add child notification. 78 * 79 * New notification must have the same group key as other notifications in group. 80 */ addNotification(AlertEntry alertEntry)81 public void addNotification(AlertEntry alertEntry) { 82 assertSameGroupKey(alertEntry.getStatusBarNotification().getGroupKey()); 83 mNotifications.add(alertEntry); 84 } 85 86 /** 87 * Removes child notification. 88 * 89 * @return {@code true} if notification was removed 90 */ removeNotification(AlertEntry alertEntry)91 public boolean removeNotification(AlertEntry alertEntry) { 92 for (int i = 0; i < mNotifications.size(); i++) { 93 if (mNotifications.get(i).getKey().equals(alertEntry.getKey())) { 94 mNotifications.remove(i); 95 return true; 96 } 97 } 98 return false; 99 } 100 101 /** 102 * Set group summary notification. 103 * 104 * Group summary must have the same group key as other notifications in group. 105 */ setGroupSummaryNotification(AlertEntry groupSummaryNotification)106 public void setGroupSummaryNotification(AlertEntry groupSummaryNotification) { 107 assertSameGroupKey(groupSummaryNotification.getStatusBarNotification().getGroupKey()); 108 mGroupSummaryNotification = groupSummaryNotification; 109 } 110 setGroupKey(@onNull String groupKey)111 void setGroupKey(@NonNull String groupKey) { 112 mGroupKey = groupKey; 113 } 114 115 /** 116 * Returns the group key of this notification group. 117 * 118 * <p> {@code null} will be returned if the group key has not been set yet. 119 */ 120 @Nullable getGroupKey()121 public String getGroupKey() { 122 return mGroupKey; 123 } 124 125 /** 126 * Returns the count of how many child notifications (excluding the group summary notification) 127 * this notification group has. 128 */ getChildCount()129 public int getChildCount() { 130 return mNotifications.size(); 131 } 132 133 /** 134 * Returns true when it has a group summary notification and >1 child notifications 135 */ isGroup()136 public boolean isGroup() { 137 return mGroupSummaryNotification != null && getChildCount() > 1; 138 } 139 140 /** 141 * Return true if this group is a header, footer, recents header or older header. 142 */ isHeaderOrFooter()143 public boolean isHeaderOrFooter() { 144 return isHeader() || isFooter() || isOlderHeader() || isRecentsHeader(); 145 } 146 147 /** 148 * Return true if the header is set to be displayed. 149 */ isHeader()150 public boolean isHeader() { 151 return mIsHeader; 152 } 153 154 /** 155 * Set this to true if a header needs to be displayed with a title and a clear all button. 156 */ setHeader(boolean header)157 public void setHeader(boolean header) { 158 mIsHeader = header; 159 } 160 161 /** 162 * Return true if the header is set to be displayed. 163 */ isFooter()164 public boolean isFooter() { 165 return mIsFooter; 166 } 167 168 /** 169 * Set this to true if a footer needs to be displayed with a clear all button. 170 */ setFooter(boolean footer)171 public void setFooter(boolean footer) { 172 mIsFooter = footer; 173 } 174 175 /** 176 * Return true if the recents header is set to be displayed. 177 */ isRecentsHeader()178 public boolean isRecentsHeader() { 179 return mIsRecentsHeader; 180 } 181 182 /** 183 * Set this to true if a header is a recents header. 184 */ setRecentsHeader(boolean isRecentsHeader)185 public void setRecentsHeader(boolean isRecentsHeader) { 186 mIsRecentsHeader = isRecentsHeader; 187 } 188 189 /** 190 * Return true if the older notifications header is set to be displayed. 191 */ isOlderHeader()192 public boolean isOlderHeader() { 193 return mIsOlderHeader; 194 } 195 196 /** 197 * Set this to true if a header is a older notifications header. 198 */ setOlderHeader(boolean isOlderHeader)199 public void setOlderHeader(boolean isOlderHeader) { 200 mIsOlderHeader = isOlderHeader; 201 } 202 203 /** 204 * Return true if the notification group has been seen. 205 */ isSeen()206 public boolean isSeen() { 207 return mIsSeen; 208 } 209 210 /** 211 * Set this to true if the notification group has been seen. 212 */ setSeen(boolean isSeen)213 public void setSeen(boolean isSeen) { 214 mIsSeen = isSeen; 215 } 216 217 /** 218 * Returns true if this group is not a header or footer and all of the notifications it holds 219 * are dismissible by user action. 220 */ isDismissible()221 public boolean isDismissible() { 222 if (mIsHeader || mIsFooter) { 223 return false; 224 } 225 226 for (AlertEntry notification : mNotifications) { 227 boolean isForeground = 228 (notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) 229 != 0; 230 if (isForeground || notification.getStatusBarNotification().isOngoing()) { 231 return false; 232 } 233 } 234 return true; 235 } 236 237 /** 238 * Returns the list of the child notifications. 239 */ getChildNotifications()240 public List<AlertEntry> getChildNotifications() { 241 return mNotifications; 242 } 243 244 /** 245 * Returns the group summary notification. 246 */ 247 @Nullable getGroupSummaryNotification()248 public AlertEntry getGroupSummaryNotification() { 249 return mGroupSummaryNotification; 250 } 251 252 /** 253 * Sets the list of child notification titles. 254 */ setChildTitles(List<String> childTitles)255 public void setChildTitles(List<String> childTitles) { 256 mChildTitles = childTitles; 257 } 258 259 /** 260 * Returns the list of child notification titles. 261 */ 262 @Nullable getChildTitles()263 public List<String> getChildTitles() { 264 return mChildTitles; 265 } 266 267 /** 268 * Generates the list of the child notification titles for a group summary notification. 269 */ generateChildTitles()270 public List<String> generateChildTitles() { 271 List<String> titles = new ArrayList<>(); 272 273 for (AlertEntry notification : mNotifications) { 274 Bundle extras = notification.getNotification().extras; 275 if (extras.containsKey(Notification.EXTRA_TITLE)) { 276 titles.add(extras.getString(Notification.EXTRA_TITLE)); 277 } else if (extras.containsKey(Notification.EXTRA_TITLE_BIG)) { 278 titles.add(extras.getString(Notification.EXTRA_TITLE_BIG)); 279 } else if (extras.containsKey(Notification.EXTRA_MESSAGES)) { 280 List<Notification.MessagingStyle.Message> messages = 281 Notification.MessagingStyle.Message.getMessagesFromBundleArray( 282 extras.getParcelableArray(Notification.EXTRA_MESSAGES)); 283 Notification.MessagingStyle.Message lastMessage = messages.get(messages.size() - 1); 284 titles.add(lastMessage.getSenderPerson().getName().toString()); 285 } else if (extras.containsKey(Notification.EXTRA_SUB_TEXT)) { 286 titles.add(extras.getString(Notification.EXTRA_SUB_TEXT)); 287 } 288 } 289 290 return titles; 291 } 292 293 /** 294 * Returns a single notification that represents this NotificationGroup: 295 * 296 * <p> If the NotificationGroup is a valid grouped notification or has no child notifications, 297 * the group summary notification is returned. 298 * 299 * <p> If the NotificationGroup has only 1 child notification, 300 * or has more than 1 child notifications without a valid group summary, 301 * the first child notification is returned. 302 * 303 * @return the notification that represents this NotificationGroup 304 */ getSingleNotification()305 public AlertEntry getSingleNotification() { 306 if (isGroup() || getChildCount() == 0) { 307 return getGroupSummaryNotification(); 308 } else { 309 return mNotifications.get(0); 310 } 311 } 312 getNotificationForSorting()313 AlertEntry getNotificationForSorting() { 314 if (mGroupSummaryNotification != null) { 315 return getGroupSummaryNotification(); 316 } 317 return getSingleNotification(); 318 } 319 assertSameGroupKey(String groupKey)320 private void assertSameGroupKey(String groupKey) { 321 if (mGroupKey == null) { 322 setGroupKey(groupKey); 323 } else if (!mGroupKey.equals(groupKey)) { 324 throw new IllegalStateException( 325 "Group key mismatch when adding a notification to a group. " + 326 "mGroupKey: " + mGroupKey + "; groupKey:" + groupKey); 327 } 328 } 329 330 @Override toString()331 public String toString() { 332 return mGroupKey + ": " + mNotifications.toString(); 333 } 334 } 335