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