Home | History | Annotate | Download | only in statusbar
      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 
     53         public Entry(StatusBarNotification n, StatusBarIconView ic) {
     54             this.key = n.getKey();
     55             this.notification = n;
     56             this.icon = ic;
     57         }
     58         public void setBigContentView(View bigContentView) {
     59             this.expandedBig = bigContentView;
     60             row.setExpandable(bigContentView != null);
     61         }
     62         public View getBigContentView() {
     63             return expandedBig;
     64         }
     65         public View getPublicContentView() { return expandedPublic; }
     66 
     67         public void setInterruption() {
     68             interruption = true;
     69         }
     70 
     71         public boolean hasInterrupted() {
     72             return interruption;
     73         }
     74 
     75         /**
     76          * Resets the notification entry to be re-used.
     77          */
     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 
    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      */
    155     public ArrayList<Entry> getActiveNotifications() {
    156         return mSortedAndFiltered;
    157     }
    158 
    159     public Entry get(String key) {
    160         return mEntries.get(key);
    161     }
    162 
    163     public void add(Entry entry, RankingMap ranking) {
    164         mEntries.put(entry.notification.getKey(), entry);
    165         updateRankingAndSort(ranking);
    166     }
    167 
    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 
    175     public void updateRanking(RankingMap ranking) {
    176         updateRankingAndSort(ranking);
    177     }
    178 
    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 
    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 
    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.
    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 
    239     public boolean isGroupWithSummary(String groupKey) {
    240         return mGroupsWithSummaries.contains(groupKey);
    241     }
    242 
    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      */
    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).
    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 
    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 
    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 
    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 {
    327         public boolean shouldHideSensitiveContents(int userid);
    328         public boolean isDeviceProvisioned();
    329         public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
    330         public String getCurrentMediaNotificationKey();
    331     }
    332 }
    333