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