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