blob: 54959d920ad66c138d03849ca02a0380fd7ecb69 [file] [log] [blame]
Selim Cinek25fd4e2b2015-02-20 17:46:07 +01001/*
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
17package com.android.systemui.statusbar.phone;
18
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010019import android.service.notification.StatusBarNotification;
Selim Cinek23c80342016-03-17 18:27:36 -070020import android.support.annotation.Nullable;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010021
22import com.android.systemui.statusbar.ExpandableNotificationRow;
23import com.android.systemui.statusbar.NotificationData;
Selim Cinek9c4c4142015-12-04 16:44:56 -080024import com.android.systemui.statusbar.StatusBarState;
Selim Cinekef5127e2015-12-21 16:55:58 -080025import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010026
27import java.util.HashMap;
28import java.util.HashSet;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010029
30/**
31 * A class to handle notifications and their corresponding groups.
32 */
Selim Cinekef5127e2015-12-21 16:55:58 -080033public class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChangedListener {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010034
35 private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
36 private OnGroupChangeListener mListener;
37 private int mBarState = -1;
Selim Cinek23c80342016-03-17 18:27:36 -070038 private HashMap<String, StatusBarNotification> mHeadsUpedEntries = new HashMap<>();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010039
40 public void setOnGroupChangeListener(OnGroupChangeListener listener) {
41 mListener = listener;
42 }
43
44 public boolean isGroupExpanded(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -080045 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010046 if (group == null) {
47 return false;
48 }
49 return group.expanded;
50 }
51
52 public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
Selim Cinekef5127e2015-12-21 16:55:58 -080053 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010054 if (group == null) {
55 return;
56 }
57 setGroupExpanded(group, expanded);
58 }
59
60 private void setGroupExpanded(NotificationGroup group, boolean expanded) {
61 group.expanded = expanded;
62 if (group.summary != null) {
63 mListener.onGroupExpansionChanged(group.summary.row, expanded);
64 }
65 }
66
67 public void onEntryRemoved(NotificationData.Entry removed) {
68 onEntryRemovedInternal(removed, removed.notification);
69 }
70
71 /**
72 * An entry was removed.
73 *
74 * @param removed the removed entry
75 * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
76 * notification
77 */
78 private void onEntryRemovedInternal(NotificationData.Entry removed,
79 final StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -080080 String groupKey = getGroupKey(sbn);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010081 final NotificationGroup group = mGroupMap.get(groupKey);
Selim Cinek0b4aeab2015-09-01 17:07:38 -070082 if (group == null) {
83 // When an app posts 2 different notifications as summary of the same group, then a
84 // cancellation of the first notification removes this group.
85 // This situation is not supported and we will not allow such notifications anymore in
86 // the close future. See b/23676310 for reference.
87 return;
88 }
Selim Cinekef5127e2015-12-21 16:55:58 -080089 if (isGroupChild(sbn)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010090 group.children.remove(removed);
Selim Cineke73ad212015-11-03 19:11:08 -080091 } else {
92 group.summary = null;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010093 }
Selim Cinek2a739342016-03-17 10:28:55 -070094 updateSuppression(group);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010095 if (group.children.isEmpty()) {
96 if (group.summary == null) {
97 mGroupMap.remove(groupKey);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010098 }
99 }
100 }
101
Selim Cinekef5127e2015-12-21 16:55:58 -0800102 public void onEntryAdded(final NotificationData.Entry added) {
103 final StatusBarNotification sbn = added.notification;
104 boolean isGroupChild = isGroupChild(sbn);
105 String groupKey = getGroupKey(sbn);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100106 NotificationGroup group = mGroupMap.get(groupKey);
107 if (group == null) {
108 group = new NotificationGroup();
109 mGroupMap.put(groupKey, group);
110 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800111 if (isGroupChild) {
Selim Cineke73ad212015-11-03 19:11:08 -0800112 group.children.add(added);
Selim Cinek2a739342016-03-17 10:28:55 -0700113 updateSuppression(group);
Selim Cineke73ad212015-11-03 19:11:08 -0800114 } else {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100115 group.summary = added;
Selim Cinekb5605e52015-02-20 18:21:41 +0100116 group.expanded = added.row.areChildrenExpanded();
Selim Cinek2a739342016-03-17 10:28:55 -0700117 updateSuppression(group);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100118 if (!group.children.isEmpty()) {
119 mListener.onGroupCreatedFromChildren(group);
120 }
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100121 }
122 }
123
Selim Cinek2a739342016-03-17 10:28:55 -0700124 private void updateSuppression(NotificationGroup group) {
Selim Cinek23c80342016-03-17 18:27:36 -0700125 if (group == null) {
126 return;
127 }
Selim Cinek2a739342016-03-17 10:28:55 -0700128 boolean prevSuppressed = group.suppressed;
Selim Cinek23c80342016-03-17 18:27:36 -0700129 group.suppressed = group.summary != null && !group.expanded
130 && (group.children.size() == 1
131 || (group.children.size() == 0
132 && !group.summary.notification.getNotification().isGroupChild()
133 && hasIsolatedChildren(group)));
Selim Cinek2a739342016-03-17 10:28:55 -0700134 if (prevSuppressed != group.suppressed) {
135 mListener.onGroupsChanged();
136 }
137 }
138
Selim Cinek23c80342016-03-17 18:27:36 -0700139 private boolean hasIsolatedChildren(NotificationGroup group) {
140 return getNumberOfIsolatedChildren(group.summary.notification.getGroupKey()) != 0;
141 }
142
143 private int getNumberOfIsolatedChildren(String groupKey) {
144 int count = 0;
145 for (StatusBarNotification sbn : mHeadsUpedEntries.values()) {
146 if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
147 count++;
148 }
149 }
150 return count;
151 }
152
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100153 public void onEntryUpdated(NotificationData.Entry entry,
154 StatusBarNotification oldNotification) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800155 if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100156 onEntryRemovedInternal(entry, oldNotification);
157 }
158 onEntryAdded(entry);
Selim Cinek23c80342016-03-17 18:27:36 -0700159 if (mHeadsUpedEntries.containsKey(entry.key)) {
160 mHeadsUpedEntries.put(entry.key, entry.notification);
161 String oldKey = oldNotification.getGroupKey();
162 String newKey = entry.notification.getGroupKey();
163 if (!oldKey.equals(newKey)) {
164 updateSuppression(mGroupMap.get(oldKey));
165 updateSuppression(mGroupMap.get(newKey));
166 }
167 }
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100168 }
169
Selim Cinek2a739342016-03-17 10:28:55 -0700170 public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
Selim Cinek23c80342016-03-17 18:27:36 -0700171 return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100172 }
173
Selim Cinek23c80342016-03-17 18:27:36 -0700174 public boolean isOnlyChildInSuppressedGroup(StatusBarNotification sbn) {
175 return isGroupSuppressed(sbn.getGroupKey())
176 && sbn.getNotification().isGroupChild()
177 && getTotalNumberOfChildren(sbn) == 1;
Selim Cinek2a739342016-03-17 10:28:55 -0700178 }
179
Selim Cinek23c80342016-03-17 18:27:36 -0700180 private int getTotalNumberOfChildren(StatusBarNotification sbn) {
181 return getNumberOfIsolatedChildren(sbn.getGroupKey())
182 + mGroupMap.get(sbn.getGroupKey()).children.size();
183 }
184
185 private boolean isGroupSuppressed(String groupKey) {
186 NotificationGroup group = mGroupMap.get(groupKey);
Selim Cinek2a739342016-03-17 10:28:55 -0700187 return group != null && group.suppressed;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100188 }
189
Selim Cinek9c4c4142015-12-04 16:44:56 -0800190 public void setStatusBarState(int newState) {
191 if (mBarState == newState) {
192 return;
193 }
194 mBarState = newState;
195 if (mBarState == StatusBarState.KEYGUARD) {
Selim Cinek9184f9c2016-02-02 17:36:53 -0800196 collapseAllGroups();
197 }
198 }
199
200 public void collapseAllGroups() {
201 for (NotificationGroup group : mGroupMap.values()) {
202 if (group.expanded) {
203 setGroupExpanded(group, false);
Selim Cinek9c4c4142015-12-04 16:44:56 -0800204 }
Selim Cinek2a739342016-03-17 10:28:55 -0700205 updateSuppression(group);
Selim Cinek9c4c4142015-12-04 16:44:56 -0800206 }
207 }
208
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100209 /**
210 * @return whether a given notification is a child in a group which has a summary
211 */
212 public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800213 if (!isGroupChild(sbn)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100214 return false;
215 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800216 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek2a739342016-03-17 10:28:55 -0700217 if (group == null || group.summary == null || group.suppressed) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100218 return false;
219 }
220 return true;
221 }
222
Selim Cinek263398f2015-10-21 17:40:23 -0700223 /**
224 * @return whether a given notification is a summary in a group which has children
225 */
226 public boolean isSummaryOfGroup(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800227 if (!isGroupSummary(sbn)) {
Selim Cinek263398f2015-10-21 17:40:23 -0700228 return false;
229 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800230 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek263398f2015-10-21 17:40:23 -0700231 if (group == null) {
232 return false;
233 }
234 return !group.children.isEmpty();
235 }
236
Selim Cinek23c80342016-03-17 18:27:36 -0700237 /**
238 * Get the summary of a specified status bar notification. For isolated notification this return
239 * itself.
240 */
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100241 public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
Selim Cinek23c80342016-03-17 18:27:36 -0700242 return getGroupSummary(getGroupKey(sbn));
243 }
244
245 /**
246 * Similar to {@link #getGroupSummary(StatusBarNotification)} but doesn't get the visual summary
247 * but the logical summary, i.e when a child is isolated, it still returns the summary as if
248 * it wasn't isolated.
249 */
250 public ExpandableNotificationRow getLogicalGroupSummary(
251 StatusBarNotification sbn) {
252 return getGroupSummary(sbn.getGroupKey());
253 }
254
255 @Nullable
256 private ExpandableNotificationRow getGroupSummary(String groupKey) {
257 NotificationGroup group = mGroupMap.get(groupKey);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100258 return group == null ? null
259 : group.summary == null ? null
Selim Cinek23c80342016-03-17 18:27:36 -0700260 : group.summary.row;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100261 }
262
Selim Cinek83bc7832015-10-22 13:26:54 -0700263 public void toggleGroupExpansion(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800264 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek83bc7832015-10-22 13:26:54 -0700265 if (group == null) {
266 return;
267 }
268 setGroupExpanded(group, !group.expanded);
269 }
270
Selim Cinekef5127e2015-12-21 16:55:58 -0800271 private boolean isIsolated(StatusBarNotification sbn) {
Selim Cinek23c80342016-03-17 18:27:36 -0700272 return mHeadsUpedEntries.containsKey(sbn.getKey())
273 && sbn.getNotification().isGroupChild();
Selim Cinekef5127e2015-12-21 16:55:58 -0800274 }
275
276 private boolean isGroupSummary(StatusBarNotification sbn) {
277 if (isIsolated(sbn)) {
278 return true;
279 }
280 return sbn.getNotification().isGroupSummary();
281 }
282 private boolean isGroupChild(StatusBarNotification sbn) {
283 if (isIsolated(sbn)) {
284 return false;
285 }
286 return sbn.getNotification().isGroupChild();
287 }
288
289 private String getGroupKey(StatusBarNotification sbn) {
290 if (isIsolated(sbn)) {
291 return sbn.getKey();
292 }
293 return sbn.getGroupKey();
294 }
295
296 @Override
297 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
298 }
299
300 @Override
301 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
302 }
303
304 @Override
305 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
306 }
307
308 @Override
309 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
310 final StatusBarNotification sbn = entry.notification;
311 if (entry.row.isHeadsUp()) {
Selim Cinek23c80342016-03-17 18:27:36 -0700312 final boolean groupChild = sbn.getNotification().isGroupChild();
313 if (groupChild) {
314 // We will be isolated now, so lets update the groups
315 onEntryRemovedInternal(entry, entry.notification);
316 }
317 mHeadsUpedEntries.put(sbn.getKey(), sbn);
318 if (groupChild) {
319 onEntryAdded(entry);
320 // We also need to update the suppression of the old group, because this call comes
321 // even before the groupManager knows about the notification at all.
322 // When the notification gets added afterwards it is already isolated and therefore
323 // it doesn't lead to an update.
324 updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
325 mListener.onGroupsChanged();
Selim Cinekef5127e2015-12-21 16:55:58 -0800326 }
327 } else {
Selim Cinek23c80342016-03-17 18:27:36 -0700328 if (mHeadsUpedEntries.containsKey(sbn.getKey())) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800329 boolean isolatedBefore = isIsolated(sbn);
330 if (isolatedBefore) {
331 // not isolated anymore, we need to update the groups
332 onEntryRemovedInternal(entry, entry.notification);
333 }
334 mHeadsUpedEntries.remove(sbn.getKey());
335 if (isolatedBefore) {
336 onEntryAdded(entry);
Selim Cinek2a739342016-03-17 10:28:55 -0700337 mListener.onGroupsChanged();
Selim Cinekef5127e2015-12-21 16:55:58 -0800338 }
339 }
340 }
341 }
342
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100343 public static class NotificationGroup {
344 public final HashSet<NotificationData.Entry> children = new HashSet<>();
345 public NotificationData.Entry summary;
346 public boolean expanded;
Selim Cinek2a739342016-03-17 10:28:55 -0700347 /**
348 * Is this notification group suppressed, i.e its summary is hidden
349 */
350 public boolean suppressed;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100351 }
352
353 public interface OnGroupChangeListener {
354 /**
355 * The expansion of a group has changed.
356 *
357 * @param changedRow the row for which the expansion has changed, which is also the summary
358 * @param expanded a boolean indicating the new expanded state
359 */
360 void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
361
362 /**
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100363 * A group of children just received a summary notification and should therefore become
364 * children of it.
365 *
366 * @param group the group created
367 */
368 void onGroupCreatedFromChildren(NotificationGroup group);
Selim Cinekef5127e2015-12-21 16:55:58 -0800369
370 /**
Selim Cinek2a739342016-03-17 10:28:55 -0700371 * The groups have changed. This can happen if the isolation of a child has changes or if a
372 * group became suppressed / unsuppressed
Selim Cinekef5127e2015-12-21 16:55:58 -0800373 */
Selim Cinek2a739342016-03-17 10:28:55 -0700374 void onGroupsChanged();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100375 }
376}