1 /*
2  * Copyright (C) 2015 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 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.app.Notification;
20 import android.service.notification.StatusBarNotification;
21 
22 import com.android.systemui.statusbar.ExpandableNotificationRow;
23 import com.android.systemui.statusbar.NotificationData;
24 import com.android.systemui.statusbar.StatusBarState;
25 
26 import java.util.HashMap;
27 import java.util.HashSet;
28 
29 /**
30  * A class to handle notifications and their corresponding groups.
31  */
32 public class NotificationGroupManager {
33 
34     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
35     private OnGroupChangeListener mListener;
36     private int mBarState = -1;
37 
setOnGroupChangeListener(OnGroupChangeListener listener)38     public void setOnGroupChangeListener(OnGroupChangeListener listener) {
39         mListener = listener;
40     }
41 
isGroupExpanded(StatusBarNotification sbn)42     public boolean isGroupExpanded(StatusBarNotification sbn) {
43         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
44         if (group == null) {
45             return false;
46         }
47         return group.expanded;
48     }
49 
setGroupExpanded(StatusBarNotification sbn, boolean expanded)50     public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
51         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
52         if (group == null) {
53             return;
54         }
55         setGroupExpanded(group, expanded);
56     }
57 
setGroupExpanded(NotificationGroup group, boolean expanded)58     private void setGroupExpanded(NotificationGroup group, boolean expanded) {
59         group.expanded = expanded;
60         if (group.summary != null) {
61             mListener.onGroupExpansionChanged(group.summary.row, expanded);
62         }
63     }
64 
onEntryRemoved(NotificationData.Entry removed)65     public void onEntryRemoved(NotificationData.Entry removed) {
66         onEntryRemovedInternal(removed, removed.notification);
67     }
68 
69     /**
70      * An entry was removed.
71      *
72      * @param removed the removed entry
73      * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
74      *            notification
75      */
onEntryRemovedInternal(NotificationData.Entry removed, final StatusBarNotification sbn)76     private void onEntryRemovedInternal(NotificationData.Entry removed,
77             final StatusBarNotification sbn) {
78         Notification notif = sbn.getNotification();
79         String groupKey = sbn.getGroupKey();
80         final NotificationGroup group = mGroupMap.get(groupKey);
81         if (group == null) {
82             // When an app posts 2 different notifications as summary of the same group, then a
83             // cancellation of the first notification removes this group.
84             // This situation is not supported and we will not allow such notifications anymore in
85             // the close future. See b/23676310 for reference.
86             return;
87         }
88         if (notif.isGroupSummary()) {
89             group.summary = null;
90         } else {
91             group.children.remove(removed);
92         }
93         if (group.children.isEmpty()) {
94             if (group.summary == null) {
95                 mGroupMap.remove(groupKey);
96             } else {
97                 if (group.expanded) {
98                     // only the summary is left. Change it to unexpanded in a few ms. We do this to
99                     // avoid raceconditions
100                     removed.row.post(new Runnable() {
101                         @Override
102                         public void run() {
103                             if (group.children.isEmpty()) {
104                                 setGroupExpanded(sbn, false);
105                             }
106                         }
107                     });
108                 } else {
109                     group.summary.row.updateExpandButton();
110                 }
111             }
112         }
113     }
114 
onEntryAdded(NotificationData.Entry added)115     public void onEntryAdded(NotificationData.Entry added) {
116         StatusBarNotification sbn = added.notification;
117         Notification notif = sbn.getNotification();
118         String groupKey = sbn.getGroupKey();
119         NotificationGroup group = mGroupMap.get(groupKey);
120         if (group == null) {
121             group = new NotificationGroup();
122             mGroupMap.put(groupKey, group);
123         }
124         if (notif.isGroupSummary()) {
125             group.summary = added;
126             group.expanded = added.row.areChildrenExpanded();
127             if (!group.children.isEmpty()) {
128                 mListener.onGroupCreatedFromChildren(group);
129             }
130         } else {
131             group.children.add(added);
132             if (group.summary != null && group.children.size() == 1 && !group.expanded) {
133                 group.summary.row.updateExpandButton();
134             }
135         }
136     }
137 
onEntryUpdated(NotificationData.Entry entry, StatusBarNotification oldNotification)138     public void onEntryUpdated(NotificationData.Entry entry,
139             StatusBarNotification oldNotification) {
140         if (mGroupMap.get(oldNotification.getGroupKey()) != null) {
141             onEntryRemovedInternal(entry, oldNotification);
142         }
143         onEntryAdded(entry);
144     }
145 
isVisible(StatusBarNotification sbn)146     public boolean isVisible(StatusBarNotification sbn) {
147         if (!sbn.getNotification().isGroupChild()) {
148             return true;
149         }
150         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
151         if (group != null && (group.expanded || group.summary == null)) {
152             return true;
153         }
154         return false;
155     }
156 
hasGroupChildren(StatusBarNotification sbn)157     public boolean hasGroupChildren(StatusBarNotification sbn) {
158         if (areGroupsProhibited()) {
159             return false;
160         }
161         if (!sbn.getNotification().isGroupSummary()) {
162             return false;
163         }
164         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
165         if (group == null) {
166             return false;
167         }
168         return !group.children.isEmpty();
169     }
170 
setStatusBarState(int newState)171     public void setStatusBarState(int newState) {
172         if (mBarState == newState) {
173             return;
174         }
175         boolean prohibitedBefore = areGroupsProhibited();
176         mBarState = newState;
177         boolean nowProhibited = areGroupsProhibited();
178         if (nowProhibited != prohibitedBefore) {
179             if (nowProhibited) {
180                 for (NotificationGroup group : mGroupMap.values()) {
181                     if (group.expanded) {
182                         setGroupExpanded(group, false);
183                     }
184                 }
185             }
186             mListener.onGroupsProhibitedChanged();
187         }
188     }
189 
areGroupsProhibited()190     private boolean areGroupsProhibited() {
191         return mBarState == StatusBarState.KEYGUARD;
192     }
193 
194     /**
195      * @return whether a given notification is a child in a group which has a summary
196      */
isChildInGroupWithSummary(StatusBarNotification sbn)197     public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
198         if (!sbn.getNotification().isGroupChild()) {
199             return false;
200         }
201         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
202         if (group == null || group.summary == null) {
203             return false;
204         }
205         return true;
206     }
207 
getGroupSummary(StatusBarNotification sbn)208     public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
209         NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
210         return group == null ? null
211                 : group.summary == null ? null
212                 : group.summary.row;
213     }
214 
215     public static class NotificationGroup {
216         public final HashSet<NotificationData.Entry> children = new HashSet<>();
217         public NotificationData.Entry summary;
218         public boolean expanded;
219     }
220 
221     public interface OnGroupChangeListener {
222         /**
223          * The expansion of a group has changed.
224          *
225          * @param changedRow the row for which the expansion has changed, which is also the summary
226          * @param expanded a boolean indicating the new expanded state
227          */
onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded)228         void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
229 
230         /**
231          * Children group policy has changed and children may no be prohibited or allowed.
232          */
onGroupsProhibitedChanged()233         void onGroupsProhibitedChanged();
234 
235         /**
236          * A group of children just received a summary notification and should therefore become
237          * children of it.
238          *
239          * @param group the group created
240          */
onGroupCreatedFromChildren(NotificationGroup group)241         void onGroupCreatedFromChildren(NotificationGroup group);
242     }
243 }
244