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