1 /*
2  * Copyright (C) 2008 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 com.android.systemui.statusbar;
18 
19 import android.app.Notification;
20 import android.service.notification.NotificationListenerService;
21 import android.service.notification.NotificationListenerService.Ranking;
22 import android.service.notification.NotificationListenerService.RankingMap;
23 import android.service.notification.StatusBarNotification;
24 import android.util.ArrayMap;
25 import android.util.ArraySet;
26 import android.view.View;
27 
28 import java.io.PrintWriter;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Comparator;
32 
33 /**
34  * The list of currently displaying notifications.
35  */
36 public class NotificationData {
37 
38     private final Environment mEnvironment;
39 
40     public static final class Entry {
41         public String key;
42         public StatusBarNotification notification;
43         public StatusBarIconView icon;
44         public ExpandableNotificationRow row; // the outer expanded view
45         public View expanded; // the inflated RemoteViews
46         public View expandedPublic; // for insecure lockscreens
47         public View expandedBig;
48         private boolean interruption;
49         public boolean autoRedacted; // whether the redacted notification was generated by us
50         public boolean legacy; // whether the notification has a legacy, dark background
51         public int targetSdk;
52 
Entry(StatusBarNotification n, StatusBarIconView ic)53         public Entry(StatusBarNotification n, StatusBarIconView ic) {
54             this.key = n.getKey();
55             this.notification = n;
56             this.icon = ic;
57         }
setBigContentView(View bigContentView)58         public void setBigContentView(View bigContentView) {
59             this.expandedBig = bigContentView;
60             row.setExpandable(bigContentView != null);
61         }
getBigContentView()62         public View getBigContentView() {
63             return expandedBig;
64         }
getPublicContentView()65         public View getPublicContentView() { return expandedPublic; }
66 
setInterruption()67         public void setInterruption() {
68             interruption = true;
69         }
70 
hasInterrupted()71         public boolean hasInterrupted() {
72             return interruption;
73         }
74 
75         /**
76          * Resets the notification entry to be re-used.
77          */
reset()78         public void reset() {
79             // NOTE: Icon needs to be preserved for now.
80             // We should fix this at some point.
81             expanded = null;
82             expandedPublic = null;
83             expandedBig = null;
84             autoRedacted = false;
85             legacy = false;
86             if (row != null) {
87                 row.reset();
88             }
89         }
90     }
91 
92     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
93     private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
94     private ArraySet<String> mGroupsWithSummaries = new ArraySet<>();
95 
96     private RankingMap mRankingMap;
97     private final Ranking mTmpRanking = new Ranking();
98     private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
99         private final Ranking mRankingA = new Ranking();
100         private final Ranking mRankingB = new Ranking();
101 
102         @Override
103         public int compare(Entry a, Entry b) {
104             // Upsort current media notification.
105             String mediaNotification = mEnvironment.getCurrentMediaNotificationKey();
106             boolean aMedia = a.key.equals(mediaNotification);
107             boolean bMedia = b.key.equals(mediaNotification);
108             if (aMedia != bMedia) {
109                 return aMedia ? -1 : 1;
110             }
111 
112             final StatusBarNotification na = a.notification;
113             final StatusBarNotification nb = b.notification;
114 
115             // Upsort PRIORITY_MAX system notifications
116             boolean aSystemMax = na.getNotification().priority >= Notification.PRIORITY_MAX &&
117                     isSystemNotification(na);
118             boolean bSystemMax = nb.getNotification().priority >= Notification.PRIORITY_MAX &&
119                     isSystemNotification(nb);
120             if (aSystemMax != bSystemMax) {
121                 return aSystemMax ? -1 : 1;
122             }
123 
124             // RankingMap as received from NoMan.
125             if (mRankingMap != null) {
126                 mRankingMap.getRanking(a.key, mRankingA);
127                 mRankingMap.getRanking(b.key, mRankingB);
128                 return mRankingA.getRank() - mRankingB.getRank();
129             }
130 
131             int d = nb.getScore() - na.getScore();
132             if (a.interruption != b.interruption) {
133                 return a.interruption ? -1 : 1;
134             } else if (d != 0) {
135                 return d;
136             } else {
137                 return (int) (nb.getNotification().when - na.getNotification().when);
138             }
139         }
140     };
141 
NotificationData(Environment environment)142     public NotificationData(Environment environment) {
143         mEnvironment = environment;
144     }
145 
146     /**
147      * Returns the sorted list of active notifications (depending on {@link Environment}
148      *
149      * <p>
150      * This call doesn't update the list of active notifications. Call {@link #filterAndSort()}
151      * when the environment changes.
152      * <p>
153      * Don't hold on to or modify the returned list.
154      */
getActiveNotifications()155     public ArrayList<Entry> getActiveNotifications() {
156         return mSortedAndFiltered;
157     }
158 
get(String key)159     public Entry get(String key) {
160         return mEntries.get(key);
161     }
162 
add(Entry entry, RankingMap ranking)163     public void add(Entry entry, RankingMap ranking) {
164         mEntries.put(entry.notification.getKey(), entry);
165         updateRankingAndSort(ranking);
166     }
167 
remove(String key, RankingMap ranking)168     public Entry remove(String key, RankingMap ranking) {
169         Entry removed = mEntries.remove(key);
170         if (removed == null) return null;
171         updateRankingAndSort(ranking);
172         return removed;
173     }
174 
updateRanking(RankingMap ranking)175     public void updateRanking(RankingMap ranking) {
176         updateRankingAndSort(ranking);
177     }
178 
isAmbient(String key)179     public boolean isAmbient(String key) {
180         if (mRankingMap != null) {
181             mRankingMap.getRanking(key, mTmpRanking);
182             return mTmpRanking.isAmbient();
183         }
184         return false;
185     }
186 
getVisibilityOverride(String key)187     public int getVisibilityOverride(String key) {
188         if (mRankingMap != null) {
189             mRankingMap.getRanking(key, mTmpRanking);
190             return mTmpRanking.getVisibilityOverride();
191         }
192         return NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
193     }
194 
updateRankingAndSort(RankingMap ranking)195     private void updateRankingAndSort(RankingMap ranking) {
196         if (ranking != null) {
197             mRankingMap = ranking;
198         }
199         filterAndSort();
200     }
201 
202     // TODO: This should not be public. Instead the Environment should notify this class when
203     // anything changed, and this class should call back the UI so it updates itself.
filterAndSort()204     public void filterAndSort() {
205         mSortedAndFiltered.clear();
206         mGroupsWithSummaries.clear();
207 
208         final int N = mEntries.size();
209         for (int i = 0; i < N; i++) {
210             Entry entry = mEntries.valueAt(i);
211             StatusBarNotification sbn = entry.notification;
212 
213             if (shouldFilterOut(sbn)) {
214                 continue;
215             }
216 
217             if (sbn.getNotification().isGroupSummary()) {
218                 mGroupsWithSummaries.add(sbn.getGroupKey());
219             }
220             mSortedAndFiltered.add(entry);
221         }
222 
223         // Second pass: Filter out group children with summary.
224         if (!mGroupsWithSummaries.isEmpty()) {
225             final int M = mSortedAndFiltered.size();
226             for (int i = M - 1; i >= 0; i--) {
227                 Entry ent = mSortedAndFiltered.get(i);
228                 StatusBarNotification sbn = ent.notification;
229                 if (sbn.getNotification().isGroupChild() &&
230                         mGroupsWithSummaries.contains(sbn.getGroupKey())) {
231                     mSortedAndFiltered.remove(i);
232                 }
233             }
234         }
235 
236         Collections.sort(mSortedAndFiltered, mRankingComparator);
237     }
238 
isGroupWithSummary(String groupKey)239     public boolean isGroupWithSummary(String groupKey) {
240         return mGroupsWithSummaries.contains(groupKey);
241     }
242 
shouldFilterOut(StatusBarNotification sbn)243     boolean shouldFilterOut(StatusBarNotification sbn) {
244         if (!(mEnvironment.isDeviceProvisioned() ||
245                 showNotificationEvenIfUnprovisioned(sbn))) {
246             return true;
247         }
248 
249         if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
250             return true;
251         }
252 
253         if (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET &&
254                 mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) {
255             return true;
256         }
257         return false;
258     }
259 
260     /**
261      * Return whether there are any clearable notifications (that aren't errors).
262      */
hasActiveClearableNotifications()263     public boolean hasActiveClearableNotifications() {
264         for (Entry e : mSortedAndFiltered) {
265             if (e.expanded != null) { // the view successfully inflated
266                 if (e.notification.isClearable()) {
267                     return true;
268                 }
269             }
270         }
271         return false;
272     }
273 
274     // Q: What kinds of notifications should show during setup?
275     // A: Almost none! Only things coming from the system (package is "android") that also
276     // have special "kind" tags marking them as relevant for setup (see below).
showNotificationEvenIfUnprovisioned(StatusBarNotification sbn)277     public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
278         return "android".equals(sbn.getPackageName())
279                 && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
280     }
281 
dump(PrintWriter pw, String indent)282     public void dump(PrintWriter pw, String indent) {
283         int N = mSortedAndFiltered.size();
284         pw.print(indent);
285         pw.println("active notifications: " + N);
286         int active;
287         for (active = 0; active < N; active++) {
288             NotificationData.Entry e = mSortedAndFiltered.get(active);
289             dumpEntry(pw, indent, active, e);
290         }
291 
292         int M = mEntries.size();
293         pw.print(indent);
294         pw.println("inactive notifications: " + (M - active));
295         int inactiveCount = 0;
296         for (int i = 0; i < M; i++) {
297             Entry entry = mEntries.valueAt(i);
298             if (!mSortedAndFiltered.contains(entry)) {
299                 dumpEntry(pw, indent, inactiveCount, entry);
300                 inactiveCount++;
301             }
302         }
303     }
304 
dumpEntry(PrintWriter pw, String indent, int i, Entry e)305     private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
306         pw.print(indent);
307         pw.println("  [" + i + "] key=" + e.key + " icon=" + e.icon);
308         StatusBarNotification n = e.notification;
309         pw.print(indent);
310         pw.println("      pkg=" + n.getPackageName() + " id=" + n.getId() + " score=" +
311                 n.getScore());
312         pw.print(indent);
313         pw.println("      notification=" + n.getNotification());
314         pw.print(indent);
315         pw.println("      tickerText=\"" + n.getNotification().tickerText + "\"");
316     }
317 
isSystemNotification(StatusBarNotification sbn)318     private static boolean isSystemNotification(StatusBarNotification sbn) {
319         String sbnPackage = sbn.getPackageName();
320         return "android".equals(sbnPackage) || "com.android.systemui".equals(sbnPackage);
321     }
322 
323     /**
324      * Provides access to keyguard state and user settings dependent data.
325      */
326     public interface Environment {
shouldHideSensitiveContents(int userid)327         public boolean shouldHideSensitiveContents(int userid);
isDeviceProvisioned()328         public boolean isDeviceProvisioned();
isNotificationForCurrentProfiles(StatusBarNotification sbn)329         public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
getCurrentMediaNotificationKey()330         public String getCurrentMediaNotificationKey();
331     }
332 }
333