/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.Slog; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; /** * NotificationManagerService helper for auto-grouping notifications. */ public class GroupHelper { private static final String TAG = "GroupHelper"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); protected static final int AUTOGROUP_AT_COUNT = 4; protected static final String AUTOGROUP_KEY = "ranker_group"; private final Callback mCallback; // Map of user : . Only contains notifications that are not // groupd by the app (aka no group or sort key). Map>> mUngroupedNotifications = new HashMap<>(); public GroupHelper(Callback callback) {; mCallback = callback; } public void onNotificationPosted(StatusBarNotification sbn) { if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey()); try { List notificationsToGroup = new ArrayList<>(); if (!sbn.isAppGroup()) { // Not grouped by the app, add to the list of notifications for the app; // send grouping update if app exceeds the autogrouping limit. synchronized (mUngroupedNotifications) { Map> ungroupedNotificationsByUser = mUngroupedNotifications.get(sbn.getUserId()); if (ungroupedNotificationsByUser == null) { ungroupedNotificationsByUser = new HashMap<>(); } mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser); LinkedHashSet notificationsForPackage = ungroupedNotificationsByUser.get(sbn.getPackageName()); if (notificationsForPackage == null) { notificationsForPackage = new LinkedHashSet<>(); } notificationsForPackage.add(sbn.getKey()); ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage); if (notificationsForPackage.size() >= AUTOGROUP_AT_COUNT) { notificationsToGroup.addAll(notificationsForPackage); } } if (notificationsToGroup.size() > 0) { adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(), notificationsToGroup.get(0), true); adjustNotificationBundling(notificationsToGroup, true); } } else { // Grouped, but not by us. Send updates to un-autogroup, if we grouped it. maybeUngroup(sbn, false, sbn.getUserId()); } } catch (Exception e) { Slog.e(TAG, "Failure processing new notification", e); } } public void onNotificationRemoved(StatusBarNotification sbn) { try { maybeUngroup(sbn, true, sbn.getUserId()); } catch (Exception e) { Slog.e(TAG, "Error processing canceled notification", e); } } /** * Un-autogroups notifications that are now grouped by the app. */ private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) { List notificationsToUnAutogroup = new ArrayList<>(); boolean removeSummary = false; synchronized (mUngroupedNotifications) { Map> ungroupedNotificationsByUser = mUngroupedNotifications.get(sbn.getUserId()); if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) { return; } LinkedHashSet notificationsForPackage = ungroupedNotificationsByUser.get(sbn.getPackageName()); if (notificationsForPackage == null || notificationsForPackage.size() == 0) { return; } if (notificationsForPackage.remove(sbn.getKey())) { if (!notificationGone) { // Add the current notification to the ungrouping list if it still exists. notificationsToUnAutogroup.add(sbn.getKey()); } } // If the status change of this notification has brought the number of loose // notifications to zero, remove the summary and un-autogroup. if (notificationsForPackage.size() == 0) { removeSummary = true; } } if (removeSummary) { adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false); } if (notificationsToUnAutogroup.size() > 0) { adjustNotificationBundling(notificationsToUnAutogroup, false); } } private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey, boolean summaryNeeded) { if (summaryNeeded) { mCallback.addAutoGroupSummary(userId, packageName, triggeringKey); } else { mCallback.removeAutoGroupSummary(userId, packageName); } } private void adjustNotificationBundling(List keys, boolean group) { for (String key : keys) { if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group); if (group) { mCallback.addAutoGroup(key); } else { mCallback.removeAutoGroup(key); } } } protected interface Callback { void addAutoGroup(String key); void removeAutoGroup(String key); void addAutoGroupSummary(int userId, String pkg, String triggeringKey); void removeAutoGroupSummary(int user, String pkg); } }