• 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 
17 package android.ext.services.notification;
18 
19 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
20 
21 import android.os.Bundle;
22 import android.os.UserHandle;
23 import android.service.notification.Adjustment;
24 import android.service.notification.NotificationRankerService;
25 import android.service.notification.StatusBarNotification;
26 import android.util.Log;
27 import android.util.Slog;
28 
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.LinkedHashSet;
32 import java.util.List;
33 import java.util.Map;
34 
35 import android.ext.services.R;
36 
37 /**
38  * Class that provides an updatable ranker module for the notification manager..
39  */
40 public final class Ranker extends NotificationRankerService {
41     private static final String TAG = "RocketRanker";
42     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
43 
44     private static final int AUTOBUNDLE_AT_COUNT = 4;
45     private static final String AUTOBUNDLE_KEY = "ranker_bundle";
46 
47     // Map of user : <Map of package : notification keys>. Only contains notifications that are not
48     // bundled by the app (aka no group or sort key).
49     Map<Integer, Map<String, LinkedHashSet<String>>> mUnbundledNotifications;
50 
51     @Override
onNotificationEnqueued(StatusBarNotification sbn, int importance, boolean user)52     public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
53             boolean user) {
54         if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
55         return null;
56     }
57 
58     @Override
onNotificationPosted(StatusBarNotification sbn)59     public void onNotificationPosted(StatusBarNotification sbn) {
60         if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
61         try {
62             List<String> notificationsToBundle = new ArrayList<>();
63             if (!sbn.isAppGroup()) {
64                 // Not grouped by the app, add to the list of notifications for the app;
65                 // send bundling update if app exceeds the autobundling limit.
66                 synchronized (mUnbundledNotifications) {
67                     Map<String, LinkedHashSet<String>> unbundledNotificationsByUser
68                             = mUnbundledNotifications.get(sbn.getUserId());
69                     if (unbundledNotificationsByUser == null) {
70                         unbundledNotificationsByUser = new HashMap<>();
71                     }
72                     mUnbundledNotifications.put(sbn.getUserId(), unbundledNotificationsByUser);
73                     LinkedHashSet<String> notificationsForPackage
74                             = unbundledNotificationsByUser.get(sbn.getPackageName());
75                     if (notificationsForPackage == null) {
76                         notificationsForPackage = new LinkedHashSet<>();
77                     }
78 
79                     notificationsForPackage.add(sbn.getKey());
80                     unbundledNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
81 
82                     if (notificationsForPackage.size() >= AUTOBUNDLE_AT_COUNT) {
83                         for (String key : notificationsForPackage) {
84                             notificationsToBundle.add(key);
85                         }
86                     }
87                 }
88                 if (notificationsToBundle.size() > 0) {
89                     adjustAutobundlingSummary(sbn.getPackageName(), notificationsToBundle.get(0),
90                             true, sbn.getUserId());
91                     adjustNotificationBundling(sbn.getPackageName(), notificationsToBundle, true,
92                             sbn.getUserId());
93                 }
94             } else {
95                 // Grouped, but not by us. Send updates to unautobundle, if we bundled it.
96                 maybeUnbundle(sbn, false, sbn.getUserId());
97             }
98         } catch (Exception e) {
99             Slog.e(TAG, "Failure processing new notification", e);
100         }
101     }
102 
103     @Override
onNotificationRemoved(StatusBarNotification sbn)104     public void onNotificationRemoved(StatusBarNotification sbn) {
105         try {
106             maybeUnbundle(sbn, true, sbn.getUserId());
107         } catch (Exception e) {
108             Slog.e(TAG, "Error processing canceled notification", e);
109         }
110     }
111 
112     /**
113      * Un-autobundles notifications that are now grouped by the app. Additionally cancels
114      * autobundling if the status change of this notification resulted in the loose notification
115      * count being under the limit.
116      */
maybeUnbundle(StatusBarNotification sbn, boolean notificationGone, int user)117     private void maybeUnbundle(StatusBarNotification sbn, boolean notificationGone, int user) {
118         List<String> notificationsToUnAutobundle = new ArrayList<>();
119         boolean removeSummary = false;
120         synchronized (mUnbundledNotifications) {
121             Map<String, LinkedHashSet<String>> unbundledNotificationsByUser
122                     = mUnbundledNotifications.get(sbn.getUserId());
123             if (unbundledNotificationsByUser == null || unbundledNotificationsByUser.size() == 0) {
124                 return;
125             }
126             LinkedHashSet<String> notificationsForPackage
127                     = unbundledNotificationsByUser.get(sbn.getPackageName());
128             if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
129                 return;
130             }
131             if (notificationsForPackage.remove(sbn.getKey())) {
132                 if (!notificationGone) {
133                     // Add the current notification to the unbundling list if it still exists.
134                     notificationsToUnAutobundle.add(sbn.getKey());
135                 }
136                 // If the status change of this notification has brought the number of loose
137                 // notifications back below the limit, remove the summary and un-autobundle.
138                 if (notificationsForPackage.size() == AUTOBUNDLE_AT_COUNT - 1) {
139                     removeSummary = true;
140                     for (String key : notificationsForPackage) {
141                         notificationsToUnAutobundle.add(key);
142                     }
143                 }
144             }
145         }
146         if (notificationsToUnAutobundle.size() > 0) {
147             if (removeSummary) {
148                 adjustAutobundlingSummary(sbn.getPackageName(), null, false, user);
149             }
150             adjustNotificationBundling(sbn.getPackageName(), notificationsToUnAutobundle, false,
151                     user);
152         }
153     }
154 
155     @Override
onListenerConnected()156     public void onListenerConnected() {
157         if (DEBUG) Log.i(TAG, "CONNECTED");
158         mUnbundledNotifications = new HashMap<>();
159         for (StatusBarNotification sbn : getActiveNotifications()) {
160             onNotificationPosted(sbn);
161         }
162     }
163 
adjustAutobundlingSummary(String packageName, String key, boolean summaryNeeded, int user)164     private void adjustAutobundlingSummary(String packageName, String key, boolean summaryNeeded,
165             int user) {
166         Bundle signals = new Bundle();
167         if (summaryNeeded) {
168             signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, true);
169             signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
170         } else {
171             signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false);
172         }
173         Adjustment adjustment = new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
174                 getContext().getString(R.string.notification_ranker_autobundle_explanation), null,
175                 user);
176         if (DEBUG) {
177             Log.i(TAG, "Summary update for: " + packageName + " "
178                     + (summaryNeeded ? "adding" : "removing"));
179         }
180         try {
181             adjustNotification(adjustment);
182         } catch (Exception e) {
183             Slog.e(TAG, "Adjustment failed", e);
184         }
185 
186     }
adjustNotificationBundling(String packageName, List<String> keys, boolean bundle, int user)187     private void adjustNotificationBundling(String packageName, List<String> keys, boolean bundle,
188             int user) {
189         List<Adjustment> adjustments = new ArrayList<>();
190         for (String key : keys) {
191             adjustments.add(createBundlingAdjustment(packageName, key, bundle, user));
192             if (DEBUG) Log.i(TAG, "Sending bundling adjustment for: " + key);
193         }
194         try {
195             adjustNotifications(adjustments);
196         } catch (Exception e) {
197             Slog.e(TAG, "Adjustments failed", e);
198         }
199     }
200 
createBundlingAdjustment(String packageName, String key, boolean bundle, int user)201     private Adjustment createBundlingAdjustment(String packageName, String key, boolean bundle,
202             int user) {
203         Bundle signals = new Bundle();
204         if (bundle) {
205             signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
206         } else {
207             signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
208         }
209         return new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
210                 getContext().getString(R.string.notification_ranker_autobundle_explanation),
211                 null, user);
212     }
213 
214 }