• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.server.notification;
17 
18 import android.service.notification.StatusBarNotification;
19 import android.util.ArrayMap;
20 import android.util.ArraySet;
21 import android.util.Log;
22 import android.util.Slog;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Map;
31 
32 /**
33  * NotificationManagerService helper for auto-grouping notifications.
34  */
35 public class GroupHelper {
36     private static final String TAG = "GroupHelper";
37     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
38 
39     protected static final String AUTOGROUP_KEY = "ranker_group";
40 
41     private final Callback mCallback;
42     private final int mAutoGroupAtCount;
43 
44     // count the number of ongoing notifications per group
45     // userId|packageName -> (set of ongoing notifications that aren't in an app group)
46     final ArrayMap<String, ArraySet<String>>
47             mOngoingGroupCount = new ArrayMap<>();
48 
49     // Map of user : <Map of package : notification keys>. Only contains notifications that are not
50     // grouped by the app (aka no group or sort key).
51     Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
52 
GroupHelper(int autoGroupAtCount, Callback callback)53     public GroupHelper(int autoGroupAtCount, Callback callback) {
54         mAutoGroupAtCount = autoGroupAtCount;
55         mCallback = callback;
56     }
57 
generatePackageKey(int userId, String pkg)58     private String generatePackageKey(int userId, String pkg) {
59         return userId + "|" + pkg;
60     }
61 
62     @VisibleForTesting
getOngoingGroupCount(int userId, String pkg)63     protected int getOngoingGroupCount(int userId, String pkg) {
64         String key = generatePackageKey(userId, pkg);
65         return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
66     }
67 
updateOngoingGroupCount(StatusBarNotification sbn, boolean add)68     private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
69         if (sbn.getNotification().isGroupSummary()) {
70             return;
71         }
72         String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
73         ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
74         if (add) {
75             notifications.add(sbn.getKey());
76             mOngoingGroupCount.put(key, notifications);
77         } else {
78             notifications.remove(sbn.getKey());
79             // we don't need to put it back if it is default
80         }
81 
82         boolean needsOngoingFlag = notifications.size() > 0;
83         mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
84     }
85 
onNotificationUpdated(StatusBarNotification childSbn)86     public void onNotificationUpdated(StatusBarNotification childSbn) {
87         updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
88     }
89 
onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists)90     public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
91         try {
92             updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());
93 
94             List<String> notificationsToGroup = new ArrayList<>();
95             if (!sbn.isAppGroup()) {
96                 // Not grouped by the app, add to the list of notifications for the app;
97                 // send grouping update if app exceeds the autogrouping limit.
98                 synchronized (mUngroupedNotifications) {
99                     Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
100                             = mUngroupedNotifications.get(sbn.getUserId());
101                     if (ungroupedNotificationsByUser == null) {
102                         ungroupedNotificationsByUser = new HashMap<>();
103                     }
104                     mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
105                     LinkedHashSet<String> notificationsForPackage
106                             = ungroupedNotificationsByUser.get(sbn.getPackageName());
107                     if (notificationsForPackage == null) {
108                         notificationsForPackage = new LinkedHashSet<>();
109                     }
110 
111                     notificationsForPackage.add(sbn.getKey());
112                     ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
113 
114                     if (notificationsForPackage.size() >= mAutoGroupAtCount
115                             || autogroupSummaryExists) {
116                         notificationsToGroup.addAll(notificationsForPackage);
117                     }
118                 }
119                 if (notificationsToGroup.size() > 0) {
120                     adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
121                             notificationsToGroup.get(0), true);
122                     adjustNotificationBundling(notificationsToGroup, true);
123                 }
124             } else {
125                 // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
126                 maybeUngroup(sbn, false, sbn.getUserId());
127             }
128 
129         } catch (Exception e) {
130             Slog.e(TAG, "Failure processing new notification", e);
131         }
132     }
133 
onNotificationRemoved(StatusBarNotification sbn)134     public void onNotificationRemoved(StatusBarNotification sbn) {
135         try {
136             updateOngoingGroupCount(sbn, false);
137             maybeUngroup(sbn, true, sbn.getUserId());
138         } catch (Exception e) {
139             Slog.e(TAG, "Error processing canceled notification", e);
140         }
141     }
142 
143     /**
144      * Un-autogroups notifications that are now grouped by the app.
145      */
maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId)146     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
147         List<String> notificationsToUnAutogroup = new ArrayList<>();
148         boolean removeSummary = false;
149         synchronized (mUngroupedNotifications) {
150             Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
151                     = mUngroupedNotifications.get(sbn.getUserId());
152             if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
153                 return;
154             }
155             LinkedHashSet<String> notificationsForPackage
156                     = ungroupedNotificationsByUser.get(sbn.getPackageName());
157             if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
158                 return;
159             }
160             if (notificationsForPackage.remove(sbn.getKey())) {
161                 if (!notificationGone) {
162                     // Add the current notification to the ungrouping list if it still exists.
163                     notificationsToUnAutogroup.add(sbn.getKey());
164                 }
165             }
166             // If the status change of this notification has brought the number of loose
167             // notifications to zero, remove the summary and un-autogroup.
168             if (notificationsForPackage.size() == 0) {
169                 ungroupedNotificationsByUser.remove(sbn.getPackageName());
170                 removeSummary = true;
171             }
172         }
173         if (removeSummary) {
174             adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
175         }
176         if (notificationsToUnAutogroup.size() > 0) {
177             adjustNotificationBundling(notificationsToUnAutogroup, false);
178         }
179     }
180 
adjustAutogroupingSummary(int userId, String packageName, String triggeringKey, boolean summaryNeeded)181     private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
182             boolean summaryNeeded) {
183         if (summaryNeeded) {
184             mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
185                     getOngoingGroupCount(userId, packageName) > 0);
186         } else {
187             mCallback.removeAutoGroupSummary(userId, packageName);
188         }
189     }
190 
adjustNotificationBundling(List<String> keys, boolean group)191     private void adjustNotificationBundling(List<String> keys, boolean group) {
192         for (String key : keys) {
193             if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
194             if (group) {
195                 mCallback.addAutoGroup(key);
196             } else {
197                 mCallback.removeAutoGroup(key);
198             }
199         }
200     }
201 
202     protected interface Callback {
addAutoGroup(String key)203         void addAutoGroup(String key);
removeAutoGroup(String key)204         void removeAutoGroup(String key);
addAutoGroupSummary(int userId, String pkg, String triggeringKey, boolean needsOngoingFlag)205         void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
206                 boolean needsOngoingFlag);
removeAutoGroupSummary(int user, String pkg)207         void removeAutoGroupSummary(int user, String pkg);
updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag)208         void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
209     }
210 }
211