NotificationData.java revision f079fc52f3c8e07cc2b5cc07a4518e0638c64b69
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        if (mRankingMap != null) {
159            mRankingMap.getRanking(key, mTmpRanking);
160            return mTmpRanking.isAmbient();
161        }
162        return false;
163    }
164
165    private void updateRankingAndSort(RankingMap ranking) {
166        if (ranking != null) {
167            mRankingMap = ranking;
168        }
169        filterAndSort();
170    }
171
172    // TODO: This should not be public. Instead the Environment should notify this class when
173    // anything changed, and this class should call back the UI so it updates itself.
174    public void filterAndSort() {
175        mSortedAndFiltered.clear();
176
177        ArraySet<String> groupsWithSummaries = null;
178        final int N = mEntries.size();
179        for (int i = 0; i < N; i++) {
180            Entry entry = mEntries.valueAt(i);
181            StatusBarNotification sbn = entry.notification;
182
183            if (shouldFilterOut(sbn)) {
184                continue;
185            }
186
187            if (sbn.getNotification().isGroupSummary()) {
188                if (groupsWithSummaries == null) {
189                    groupsWithSummaries = new ArraySet<>();
190                }
191                groupsWithSummaries.add(sbn.getGroupKey());
192            }
193            mSortedAndFiltered.add(entry);
194        }
195
196        // Second pass: Filter out group children with summary.
197        if (groupsWithSummaries != null) {
198            final int M = mSortedAndFiltered.size();
199            for (int i = M - 1; i >= 0; i--) {
200                Entry ent = mSortedAndFiltered.get(i);
201                StatusBarNotification sbn = ent.notification;
202                if (sbn.getNotification().isGroupChild() &&
203                        groupsWithSummaries.contains(sbn.getGroupKey())) {
204                    mSortedAndFiltered.remove(i);
205                }
206            }
207        }
208
209        Collections.sort(mSortedAndFiltered, mRankingComparator);
210    }
211
212    private boolean shouldFilterOut(StatusBarNotification sbn) {
213        if (!(mEnvironment.isDeviceProvisioned() ||
214                showNotificationEvenIfUnprovisioned(sbn))) {
215            return true;
216        }
217
218        if (!mEnvironment.isNotificationForCurrentProfiles(sbn)) {
219            return true;
220        }
221
222        if (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET &&
223                mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) {
224            return true;
225        }
226        return false;
227    }
228
229    /**
230     * Return whether there are any clearable notifications (that aren't errors).
231     */
232    public boolean hasActiveClearableNotifications() {
233        for (Entry e : mSortedAndFiltered) {
234            if (e.expanded != null) { // the view successfully inflated
235                if (e.notification.isClearable()) {
236                    return true;
237                }
238            }
239        }
240        return false;
241    }
242
243    // Q: What kinds of notifications should show during setup?
244    // A: Almost none! Only things coming from the system (package is "android") that also
245    // have special "kind" tags marking them as relevant for setup (see below).
246    public static boolean showNotificationEvenIfUnprovisioned(StatusBarNotification sbn) {
247        return "android".equals(sbn.getPackageName())
248                && sbn.getNotification().extras.getBoolean(Notification.EXTRA_ALLOW_DURING_SETUP);
249    }
250
251    public void dump(PrintWriter pw, String indent) {
252        int N = mSortedAndFiltered.size();
253        pw.print(indent);
254        pw.println("active notifications: " + N);
255        for (int i = 0; i < N; i++) {
256            NotificationData.Entry e = mSortedAndFiltered.get(i);
257            dumpEntry(pw, indent, i, e);
258        }
259
260        int M = mEntries.size();
261        pw.print(indent);
262        pw.println("inactive notifications: " + M);
263        for (int i = 0; i < M; i++) {
264            Entry entry = mEntries.valueAt(i);
265            if (!mSortedAndFiltered.contains(entry)) {
266                dumpEntry(pw, indent, i, entry);
267            }
268        }
269    }
270
271    private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
272        pw.print(indent);
273        pw.println("  [" + i + "] key=" + e.key + " icon=" + e.icon);
274        StatusBarNotification n = e.notification;
275        pw.print(indent);
276        pw.println("      pkg=" + n.getPackageName() + " id=" + n.getId() + " score=" + n
277                .getScore());
278        pw.print(indent);
279        pw.println("      notification=" + n.getNotification());
280        pw.print(indent);
281        pw.println("      tickerText=\"" + n.getNotification().tickerText + "\"");
282    }
283
284    /**
285     * Provides access to keyguard state and user settings dependent data.
286     */
287    public interface Environment {
288        public boolean shouldHideSensitiveContents(int userId);
289        public boolean isDeviceProvisioned();
290        public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
291    }
292}
293