blob: bf7e8ed59e8f144f413a4f051aacca80e9c04311 [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 Cinekef5127e2015-12-21 16:55:58 -080020import android.util.ArraySet;
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 Cinekef5127e2015-12-21 16:55:58 -080038 private ArraySet<String> mHeadsUpedEntries = new ArraySet<>();
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) {
125 boolean prevSuppressed = group.suppressed;
126 group.suppressed = group.summary != null && group.children.size() == 1 && !group.expanded;
127 if (prevSuppressed != group.suppressed) {
128 mListener.onGroupsChanged();
129 }
130 }
131
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100132 public void onEntryUpdated(NotificationData.Entry entry,
133 StatusBarNotification oldNotification) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800134 if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100135 onEntryRemovedInternal(entry, oldNotification);
136 }
137 onEntryAdded(entry);
138 }
139
Selim Cinek2a739342016-03-17 10:28:55 -0700140 public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
141 return isGroupSuppressed(sbn) && sbn.getNotification().isGroupSummary();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100142 }
143
Selim Cinek2a739342016-03-17 10:28:55 -0700144 public boolean isChildInSuppressedGroup(StatusBarNotification sbn) {
145 return isGroupSuppressed(sbn) && sbn.getNotification().isGroupChild();
146 }
147
148 private boolean isGroupSuppressed(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800149 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek2a739342016-03-17 10:28:55 -0700150 return group != null && group.suppressed;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100151 }
152
Selim Cinek9c4c4142015-12-04 16:44:56 -0800153 public void setStatusBarState(int newState) {
154 if (mBarState == newState) {
155 return;
156 }
157 mBarState = newState;
158 if (mBarState == StatusBarState.KEYGUARD) {
Selim Cinek9184f9c2016-02-02 17:36:53 -0800159 collapseAllGroups();
160 }
161 }
162
163 public void collapseAllGroups() {
164 for (NotificationGroup group : mGroupMap.values()) {
165 if (group.expanded) {
166 setGroupExpanded(group, false);
Selim Cinek9c4c4142015-12-04 16:44:56 -0800167 }
Selim Cinek2a739342016-03-17 10:28:55 -0700168 updateSuppression(group);
Selim Cinek9c4c4142015-12-04 16:44:56 -0800169 }
170 }
171
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100172 /**
173 * @return whether a given notification is a child in a group which has a summary
174 */
175 public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800176 if (!isGroupChild(sbn)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100177 return false;
178 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800179 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek2a739342016-03-17 10:28:55 -0700180 if (group == null || group.summary == null || group.suppressed) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100181 return false;
182 }
183 return true;
184 }
185
Selim Cinek263398f2015-10-21 17:40:23 -0700186 /**
187 * @return whether a given notification is a summary in a group which has children
188 */
189 public boolean isSummaryOfGroup(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800190 if (!isGroupSummary(sbn)) {
Selim Cinek263398f2015-10-21 17:40:23 -0700191 return false;
192 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800193 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek263398f2015-10-21 17:40:23 -0700194 if (group == null) {
195 return false;
196 }
197 return !group.children.isEmpty();
198 }
199
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100200 public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800201 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100202 return group == null ? null
203 : group.summary == null ? null
204 : group.summary.row;
205 }
206
Selim Cinek83bc7832015-10-22 13:26:54 -0700207 public void toggleGroupExpansion(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800208 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek83bc7832015-10-22 13:26:54 -0700209 if (group == null) {
210 return;
211 }
212 setGroupExpanded(group, !group.expanded);
213 }
214
Selim Cinekef5127e2015-12-21 16:55:58 -0800215 private boolean isIsolated(StatusBarNotification sbn) {
216 return mHeadsUpedEntries.contains(sbn.getKey()) && sbn.getNotification().isGroupChild();
217 }
218
219 private boolean isGroupSummary(StatusBarNotification sbn) {
220 if (isIsolated(sbn)) {
221 return true;
222 }
223 return sbn.getNotification().isGroupSummary();
224 }
225 private boolean isGroupChild(StatusBarNotification sbn) {
226 if (isIsolated(sbn)) {
227 return false;
228 }
229 return sbn.getNotification().isGroupChild();
230 }
231
232 private String getGroupKey(StatusBarNotification sbn) {
233 if (isIsolated(sbn)) {
234 return sbn.getKey();
235 }
236 return sbn.getGroupKey();
237 }
238
239 @Override
240 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
241 }
242
243 @Override
244 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
245 }
246
247 @Override
248 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
249 }
250
251 @Override
252 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
253 final StatusBarNotification sbn = entry.notification;
254 if (entry.row.isHeadsUp()) {
255 if (!mHeadsUpedEntries.contains(sbn.getKey())) {
256 final boolean groupChild = sbn.getNotification().isGroupChild();
257 if (groupChild) {
258 // We will be isolated now, so lets update the groups
259 onEntryRemovedInternal(entry, entry.notification);
260 }
261 mHeadsUpedEntries.add(sbn.getKey());
262 if (groupChild) {
263 onEntryAdded(entry);
Selim Cinek2a739342016-03-17 10:28:55 -0700264 mListener.onGroupsChanged();
Selim Cinekef5127e2015-12-21 16:55:58 -0800265 }
266 }
267 } else {
268 if (mHeadsUpedEntries.contains(sbn.getKey())) {
269 boolean isolatedBefore = isIsolated(sbn);
270 if (isolatedBefore) {
271 // not isolated anymore, we need to update the groups
272 onEntryRemovedInternal(entry, entry.notification);
273 }
274 mHeadsUpedEntries.remove(sbn.getKey());
275 if (isolatedBefore) {
276 onEntryAdded(entry);
Selim Cinek2a739342016-03-17 10:28:55 -0700277 mListener.onGroupsChanged();
Selim Cinekef5127e2015-12-21 16:55:58 -0800278 }
279 }
280 }
281 }
282
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100283 public static class NotificationGroup {
284 public final HashSet<NotificationData.Entry> children = new HashSet<>();
285 public NotificationData.Entry summary;
286 public boolean expanded;
Selim Cinek2a739342016-03-17 10:28:55 -0700287 /**
288 * Is this notification group suppressed, i.e its summary is hidden
289 */
290 public boolean suppressed;
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100291 }
292
293 public interface OnGroupChangeListener {
294 /**
295 * The expansion of a group has changed.
296 *
297 * @param changedRow the row for which the expansion has changed, which is also the summary
298 * @param expanded a boolean indicating the new expanded state
299 */
300 void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
301
302 /**
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100303 * A group of children just received a summary notification and should therefore become
304 * children of it.
305 *
306 * @param group the group created
307 */
308 void onGroupCreatedFromChildren(NotificationGroup group);
Selim Cinekef5127e2015-12-21 16:55:58 -0800309
310 /**
Selim Cinek2a739342016-03-17 10:28:55 -0700311 * The groups have changed. This can happen if the isolation of a child has changes or if a
312 * group became suppressed / unsuppressed
Selim Cinekef5127e2015-12-21 16:55:58 -0800313 */
Selim Cinek2a739342016-03-17 10:28:55 -0700314 void onGroupsChanged();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100315 }
316}