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