• 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.Log;
20 import android.util.Slog;
21 
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.LinkedHashSet;
25 import java.util.List;
26 import java.util.Map;
27 
28 /**
29  * NotificationManagerService helper for auto-grouping notifications.
30  */
31 public class GroupHelper {
32     private static final String TAG = "GroupHelper";
33     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
34 
35     protected static final int AUTOGROUP_AT_COUNT = 4;
36     protected static final String AUTOGROUP_KEY = "ranker_group";
37 
38     private final Callback mCallback;
39 
40     // Map of user : <Map of package : notification keys>. Only contains notifications that are not
41     // grouped by the app (aka no group or sort key).
42     Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
43 
GroupHelper(Callback callback)44     public GroupHelper(Callback callback) {;
45         mCallback = callback;
46     }
47 
onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists)48     public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
49         if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
50         try {
51             List<String> notificationsToGroup = new ArrayList<>();
52             if (!sbn.isAppGroup()) {
53                 // Not grouped by the app, add to the list of notifications for the app;
54                 // send grouping update if app exceeds the autogrouping limit.
55                 synchronized (mUngroupedNotifications) {
56                     Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
57                             = mUngroupedNotifications.get(sbn.getUserId());
58                     if (ungroupedNotificationsByUser == null) {
59                         ungroupedNotificationsByUser = new HashMap<>();
60                     }
61                     mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
62                     LinkedHashSet<String> notificationsForPackage
63                             = ungroupedNotificationsByUser.get(sbn.getPackageName());
64                     if (notificationsForPackage == null) {
65                         notificationsForPackage = new LinkedHashSet<>();
66                     }
67 
68                     notificationsForPackage.add(sbn.getKey());
69                     ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
70 
71                     if (notificationsForPackage.size() >= AUTOGROUP_AT_COUNT
72                             || autogroupSummaryExists) {
73                         notificationsToGroup.addAll(notificationsForPackage);
74                     }
75                 }
76                 if (notificationsToGroup.size() > 0) {
77                     adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
78                             notificationsToGroup.get(0), true);
79                     adjustNotificationBundling(notificationsToGroup, true);
80                 }
81             } else {
82                 // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
83                 maybeUngroup(sbn, false, sbn.getUserId());
84             }
85         } catch (Exception e) {
86             Slog.e(TAG, "Failure processing new notification", e);
87         }
88     }
89 
onNotificationRemoved(StatusBarNotification sbn)90     public void onNotificationRemoved(StatusBarNotification sbn) {
91         try {
92             maybeUngroup(sbn, true, sbn.getUserId());
93         } catch (Exception e) {
94             Slog.e(TAG, "Error processing canceled notification", e);
95         }
96     }
97 
98     /**
99      * Un-autogroups notifications that are now grouped by the app.
100      */
maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId)101     private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
102         List<String> notificationsToUnAutogroup = new ArrayList<>();
103         boolean removeSummary = false;
104         synchronized (mUngroupedNotifications) {
105             Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
106                     = mUngroupedNotifications.get(sbn.getUserId());
107             if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
108                 return;
109             }
110             LinkedHashSet<String> notificationsForPackage
111                     = ungroupedNotificationsByUser.get(sbn.getPackageName());
112             if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
113                 return;
114             }
115             if (notificationsForPackage.remove(sbn.getKey())) {
116                 if (!notificationGone) {
117                     // Add the current notification to the ungrouping list if it still exists.
118                     notificationsToUnAutogroup.add(sbn.getKey());
119                 }
120             }
121             // If the status change of this notification has brought the number of loose
122             // notifications to zero, remove the summary and un-autogroup.
123             if (notificationsForPackage.size() == 0) {
124                 ungroupedNotificationsByUser.remove(sbn.getPackageName());
125                 removeSummary = true;
126             }
127         }
128         if (removeSummary) {
129             adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
130         }
131         if (notificationsToUnAutogroup.size() > 0) {
132             adjustNotificationBundling(notificationsToUnAutogroup, false);
133         }
134     }
135 
adjustAutogroupingSummary(int userId, String packageName, String triggeringKey, boolean summaryNeeded)136     private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
137             boolean summaryNeeded) {
138         if (summaryNeeded) {
139             mCallback.addAutoGroupSummary(userId, packageName, triggeringKey);
140         } else {
141             mCallback.removeAutoGroupSummary(userId, packageName);
142         }
143     }
144 
adjustNotificationBundling(List<String> keys, boolean group)145     private void adjustNotificationBundling(List<String> keys, boolean group) {
146         for (String key : keys) {
147             if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
148             if (group) {
149                 mCallback.addAutoGroup(key);
150             } else {
151                 mCallback.removeAutoGroup(key);
152             }
153         }
154     }
155 
156     protected interface Callback {
addAutoGroup(String key)157         void addAutoGroup(String key);
removeAutoGroup(String key)158         void removeAutoGroup(String key);
addAutoGroupSummary(int userId, String pkg, String triggeringKey)159         void addAutoGroupSummary(int userId, String pkg, String triggeringKey);
removeAutoGroupSummary(int user, String pkg)160         void removeAutoGroupSummary(int user, String pkg);
161     }
162 }
163