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.Bundle;
22 import android.service.notification.StatusBarNotification;
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 
38     private String mGroupKey;
39     private final List<StatusBarNotification> mNotifications = new ArrayList<>();
40     @Nullable
41     private List<String> mChildTitles;
42     @Nullable
43     private StatusBarNotification mGroupSummaryNotification;
44 
45     private boolean mIsHeader;
46     private boolean mIsFooter;
47 
NotificationGroup()48     public NotificationGroup() {
49     }
50 
NotificationGroup(StatusBarNotification statusBarNotification)51     public NotificationGroup(StatusBarNotification statusBarNotification) {
52         addNotification(statusBarNotification);
53     }
54 
addNotification(StatusBarNotification statusBarNotification)55     public void addNotification(StatusBarNotification statusBarNotification) {
56         assertSameGroupKey(statusBarNotification.getGroupKey());
57         mNotifications.add(statusBarNotification);
58     }
59 
setGroupSummaryNotification(StatusBarNotification groupSummaryNotification)60     void setGroupSummaryNotification(StatusBarNotification groupSummaryNotification) {
61         assertSameGroupKey(groupSummaryNotification.getGroupKey());
62         mGroupSummaryNotification = groupSummaryNotification;
63     }
64 
setGroupKey(@onNull String groupKey)65     void setGroupKey(@NonNull String groupKey) {
66         mGroupKey = groupKey;
67     }
68 
69     /**
70      * Returns the group key of this notification group.
71      *
72      * <p> {@code null} will be returned if the group key has not been set yet.
73      */
74     @Nullable
getGroupKey()75     public String getGroupKey() {
76         return mGroupKey;
77     }
78 
79     /**
80      * Returns the count of how many child notifications (excluding the group summary notification)
81      * this notification group has.
82      */
getChildCount()83     public int getChildCount() {
84         return mNotifications.size();
85     }
86 
87     /**
88      * Returns true when it has a group summary notification and >1 child notifications
89      */
isGroup()90     public boolean isGroup() {
91         return mGroupSummaryNotification != null && getChildCount() > 1;
92     }
93 
94     /**
95      * Return true if the header is set to be displayed.
96      */
isHeader()97     public boolean isHeader() {
98         return mIsHeader;
99     }
100 
101     /**
102      * Set this to true if a header needs to be displayed with a title and a clear all button.
103      */
setHeader(boolean header)104     public void setHeader(boolean header) {
105         mIsHeader = header;
106     }
107 
108     /**
109      * Return true if the header is set to be displayed.
110      */
isFooter()111     public boolean isFooter() {
112         return mIsFooter;
113     }
114 
115     /**
116      * Set this to true if a footer needs to be displayed with a clear all button.
117      */
setFooter(boolean footer)118     public void setFooter(boolean footer) {
119         mIsFooter = footer;
120     }
121 
122     /**
123      * Returns true if all of the notifications this group holds is dismissible by user action.
124      */
isDismissible()125     public boolean isDismissible() {
126         for (StatusBarNotification notification : mNotifications) {
127             boolean isForeground =
128                     (notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
129                             != 0;
130             if (isForeground || notification.isOngoing()) {
131                 return false;
132             }
133         }
134         return true;
135     }
136 
137     /**
138      * Returns the list of the child notifications.
139      */
getChildNotifications()140     public List<StatusBarNotification> getChildNotifications() {
141         return mNotifications;
142     }
143 
144     /**
145      * Returns the group summary notification.
146      */
147     @Nullable
getGroupSummaryNotification()148     public StatusBarNotification getGroupSummaryNotification() {
149         return mGroupSummaryNotification;
150     }
151 
152     /**
153      * Sets the list of child notification titles.
154      */
setChildTitles(List<String> childTitles)155     public void setChildTitles(List<String> childTitles) {
156         mChildTitles = childTitles;
157     }
158 
159     /**
160      * Returns the list of child notification titles.
161      */
162     @Nullable
getChildTitles()163     public List<String> getChildTitles() {
164         return mChildTitles;
165     }
166 
167     /**
168      * Generates the list of the child notification titles for a group summary notification.
169      */
generateChildTitles()170     public List<String> generateChildTitles() {
171         List<String> titles = new ArrayList<>();
172 
173         for (StatusBarNotification notification : mNotifications) {
174             Bundle extras = notification.getNotification().extras;
175             if (extras.containsKey(Notification.EXTRA_TITLE)) {
176                 titles.add(extras.getString(Notification.EXTRA_TITLE));
177 
178             } else if (extras.containsKey(Notification.EXTRA_TITLE_BIG)) {
179                 titles.add(extras.getString(Notification.EXTRA_TITLE_BIG));
180 
181             } else if (extras.containsKey(Notification.EXTRA_MESSAGES)) {
182                 List<Notification.MessagingStyle.Message> messages =
183                         Notification.MessagingStyle.Message.getMessagesFromBundleArray(
184                                 extras.getParcelableArray(Notification.EXTRA_MESSAGES));
185                 Notification.MessagingStyle.Message lastMessage = messages.get(messages.size() - 1);
186                 titles.add(lastMessage.getSenderPerson().getName().toString());
187 
188             } else if (extras.containsKey(Notification.EXTRA_SUB_TEXT)) {
189                 titles.add(extras.getString(Notification.EXTRA_SUB_TEXT));
190             }
191         }
192 
193         return titles;
194     }
195 
196     /**
197      * Returns a single notification that represents this NotificationGroup:
198      *
199      * <p> If the NotificationGroup is a valid grouped notification or has no child notifications,
200      * the group summary notification is returned.
201      *
202      * <p> If the NotificationGroup has only 1 child notification,
203      * or has more than 1 child notifications without a valid group summary,
204      * the first child notification is returned.
205      *
206      * @return the notification that represents this NotificationGroup
207      */
getSingleNotification()208     public StatusBarNotification getSingleNotification() {
209         if (isGroup() || getChildCount() == 0) {
210             return getGroupSummaryNotification();
211 
212         } else {
213             return mNotifications.get(0);
214         }
215     }
216 
getNotificationForSorting()217     StatusBarNotification getNotificationForSorting() {
218         if (mGroupSummaryNotification != null) {
219             return getGroupSummaryNotification();
220         }
221         return getSingleNotification();
222     }
223 
assertSameGroupKey(String groupKey)224     private void assertSameGroupKey(String groupKey) {
225         if (mGroupKey == null) {
226             setGroupKey(groupKey);
227         } else if (!mGroupKey.equals(groupKey)) {
228             throw new IllegalStateException(
229                     "Group key mismatch when adding a notification to a group. " +
230                             "mGroupKey: " + mGroupKey + "; groupKey:" + groupKey);
231         }
232     }
233 }
234