blob: 3130eb9c4c2d9fce2b173690db8072879d1ad964 [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 }
94 if (group.children.isEmpty()) {
95 if (group.summary == null) {
96 mGroupMap.remove(groupKey);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +010097 }
98 }
99 }
100
Selim Cinekef5127e2015-12-21 16:55:58 -0800101 public void onEntryAdded(final NotificationData.Entry added) {
102 final StatusBarNotification sbn = added.notification;
103 boolean isGroupChild = isGroupChild(sbn);
104 String groupKey = getGroupKey(sbn);
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100105 NotificationGroup group = mGroupMap.get(groupKey);
106 if (group == null) {
107 group = new NotificationGroup();
108 mGroupMap.put(groupKey, group);
109 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800110 if (isGroupChild) {
Selim Cineke73ad212015-11-03 19:11:08 -0800111 group.children.add(added);
Selim Cineke73ad212015-11-03 19:11:08 -0800112 } else {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100113 group.summary = added;
Selim Cinekb5605e52015-02-20 18:21:41 +0100114 group.expanded = added.row.areChildrenExpanded();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100115 if (!group.children.isEmpty()) {
116 mListener.onGroupCreatedFromChildren(group);
117 }
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100118 }
119 }
120
121 public void onEntryUpdated(NotificationData.Entry entry,
122 StatusBarNotification oldNotification) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800123 if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100124 onEntryRemovedInternal(entry, oldNotification);
125 }
126 onEntryAdded(entry);
127 }
128
129 public boolean isVisible(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800130 if (!isGroupChild(sbn)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100131 return true;
132 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800133 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinekacf52ba2015-07-07 14:54:10 -0700134 if (group != null && (group.expanded || group.summary == null)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100135 return true;
136 }
137 return false;
138 }
139
140 public boolean hasGroupChildren(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800141 if (!isGroupSummary(sbn)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100142 return false;
143 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800144 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100145 if (group == null) {
146 return false;
147 }
148 return !group.children.isEmpty();
149 }
150
Selim Cinek9c4c4142015-12-04 16:44:56 -0800151 public void setStatusBarState(int newState) {
152 if (mBarState == newState) {
153 return;
154 }
155 mBarState = newState;
156 if (mBarState == StatusBarState.KEYGUARD) {
157 for (NotificationGroup group : mGroupMap.values()) {
158 if (group.expanded) {
159 setGroupExpanded(group, false);
160 }
161 }
162 }
163 }
164
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100165 /**
166 * @return whether a given notification is a child in a group which has a summary
167 */
168 public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800169 if (!isGroupChild(sbn)) {
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100170 return false;
171 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800172 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100173 if (group == null || group.summary == null) {
174 return false;
175 }
176 return true;
177 }
178
Selim Cinek263398f2015-10-21 17:40:23 -0700179 /**
180 * @return whether a given notification is a summary in a group which has children
181 */
182 public boolean isSummaryOfGroup(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800183 if (!isGroupSummary(sbn)) {
Selim Cinek263398f2015-10-21 17:40:23 -0700184 return false;
185 }
Selim Cinekef5127e2015-12-21 16:55:58 -0800186 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek263398f2015-10-21 17:40:23 -0700187 if (group == null) {
188 return false;
189 }
190 return !group.children.isEmpty();
191 }
192
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100193 public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800194 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100195 return group == null ? null
196 : group.summary == null ? null
197 : group.summary.row;
198 }
199
Selim Cinek83bc7832015-10-22 13:26:54 -0700200 public void toggleGroupExpansion(StatusBarNotification sbn) {
Selim Cinekef5127e2015-12-21 16:55:58 -0800201 NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
Selim Cinek83bc7832015-10-22 13:26:54 -0700202 if (group == null) {
203 return;
204 }
205 setGroupExpanded(group, !group.expanded);
206 }
207
Selim Cinekef5127e2015-12-21 16:55:58 -0800208 private boolean isIsolated(StatusBarNotification sbn) {
209 return mHeadsUpedEntries.contains(sbn.getKey()) && sbn.getNotification().isGroupChild();
210 }
211
212 private boolean isGroupSummary(StatusBarNotification sbn) {
213 if (isIsolated(sbn)) {
214 return true;
215 }
216 return sbn.getNotification().isGroupSummary();
217 }
218 private boolean isGroupChild(StatusBarNotification sbn) {
219 if (isIsolated(sbn)) {
220 return false;
221 }
222 return sbn.getNotification().isGroupChild();
223 }
224
225 private String getGroupKey(StatusBarNotification sbn) {
226 if (isIsolated(sbn)) {
227 return sbn.getKey();
228 }
229 return sbn.getGroupKey();
230 }
231
232 @Override
233 public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
234 }
235
236 @Override
237 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
238 }
239
240 @Override
241 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
242 }
243
244 @Override
245 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
246 final StatusBarNotification sbn = entry.notification;
247 if (entry.row.isHeadsUp()) {
248 if (!mHeadsUpedEntries.contains(sbn.getKey())) {
249 final boolean groupChild = sbn.getNotification().isGroupChild();
250 if (groupChild) {
251 // We will be isolated now, so lets update the groups
252 onEntryRemovedInternal(entry, entry.notification);
253 }
254 mHeadsUpedEntries.add(sbn.getKey());
255 if (groupChild) {
256 onEntryAdded(entry);
257 mListener.onChildIsolationChanged();
258 }
259 }
260 } else {
261 if (mHeadsUpedEntries.contains(sbn.getKey())) {
262 boolean isolatedBefore = isIsolated(sbn);
263 if (isolatedBefore) {
264 // not isolated anymore, we need to update the groups
265 onEntryRemovedInternal(entry, entry.notification);
266 }
267 mHeadsUpedEntries.remove(sbn.getKey());
268 if (isolatedBefore) {
269 onEntryAdded(entry);
270 mListener.onChildIsolationChanged();
271 }
272 }
273 }
274 }
275
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100276 public static class NotificationGroup {
277 public final HashSet<NotificationData.Entry> children = new HashSet<>();
278 public NotificationData.Entry summary;
279 public boolean expanded;
280 }
281
282 public interface OnGroupChangeListener {
283 /**
284 * The expansion of a group has changed.
285 *
286 * @param changedRow the row for which the expansion has changed, which is also the summary
287 * @param expanded a boolean indicating the new expanded state
288 */
289 void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
290
291 /**
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100292 * A group of children just received a summary notification and should therefore become
293 * children of it.
294 *
295 * @param group the group created
296 */
297 void onGroupCreatedFromChildren(NotificationGroup group);
Selim Cinekef5127e2015-12-21 16:55:58 -0800298
299 /**
300 * The isolation of a child has changed i.e it's group changes.
301 */
302 void onChildIsolationChanged();
Selim Cinek25fd4e2b2015-02-20 17:46:07 +0100303 }
304}