Home | History | Annotate | Download | only in notification
      1 /*
      2  * Copyright (C) 2014 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 package com.android.server.notification;
     17 
     18 import android.app.Notification;
     19 import android.content.Context;
     20 import android.content.pm.PackageManager.NameNotFoundException;
     21 import android.content.res.Resources;
     22 import android.graphics.Bitmap;
     23 import android.media.AudioAttributes;
     24 import android.os.UserHandle;
     25 import android.service.notification.StatusBarNotification;
     26 
     27 import com.android.internal.annotations.VisibleForTesting;
     28 
     29 import java.io.PrintWriter;
     30 import java.lang.reflect.Array;
     31 import java.util.Arrays;
     32 import java.util.Objects;
     33 
     34 /**
     35  * Holds data about notifications that should not be shared with the
     36  * {@link android.service.notification.NotificationListenerService}s.
     37  *
     38  * <p>These objects should not be mutated unless the code is synchronized
     39  * on {@link NotificationManagerService#mNotificationList}, and any
     40  * modification should be followed by a sorting of that list.</p>
     41  *
     42  * <p>Is sortable by {@link NotificationComparator}.</p>
     43  *
     44  * {@hide}
     45  */
     46 public final class NotificationRecord {
     47     final StatusBarNotification sbn;
     48     final int mOriginalFlags;
     49 
     50     NotificationUsageStats.SingleNotificationStats stats;
     51     boolean isCanceled;
     52     int score;
     53 
     54     // These members are used by NotificationSignalExtractors
     55     // to communicate with the ranking module.
     56     private float mContactAffinity;
     57     private boolean mRecentlyIntrusive;
     58 
     59     // is this notification currently being intercepted by Zen Mode?
     60     private boolean mIntercept;
     61 
     62     // The timestamp used for ranking.
     63     private long mRankingTimeMs;
     64 
     65     // Is this record an update of an old record?
     66     public boolean isUpdate;
     67     private int mPackagePriority;
     68 
     69     private int mAuthoritativeRank;
     70     private String mGlobalSortKey;
     71     private int mPackageVisibility;
     72 
     73     @VisibleForTesting
     74     public NotificationRecord(StatusBarNotification sbn, int score)
     75     {
     76         this.sbn = sbn;
     77         this.score = score;
     78         mOriginalFlags = sbn.getNotification().flags;
     79         mRankingTimeMs = calculateRankingTimeMs(0L);
     80     }
     81 
     82     // copy any notes that the ranking system may have made before the update
     83     public void copyRankingInformation(NotificationRecord previous) {
     84         mContactAffinity = previous.mContactAffinity;
     85         mRecentlyIntrusive = previous.mRecentlyIntrusive;
     86         mPackagePriority = previous.mPackagePriority;
     87         mPackageVisibility = previous.mPackageVisibility;
     88         mIntercept = previous.mIntercept;
     89         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
     90         // Don't copy mGlobalSortKey, recompute it.
     91     }
     92 
     93     public Notification getNotification() { return sbn.getNotification(); }
     94     public int getFlags() { return sbn.getNotification().flags; }
     95     public UserHandle getUser() { return sbn.getUser(); }
     96     public String getKey() { return sbn.getKey(); }
     97     /** @deprecated Use {@link #getUser()} instead. */
     98     public int getUserId() { return sbn.getUserId(); }
     99 
    100     void dump(PrintWriter pw, String prefix, Context baseContext) {
    101         final Notification notification = sbn.getNotification();
    102         pw.println(prefix + this);
    103         pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
    104         pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
    105                 + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon));
    106         pw.println(prefix + "  pri=" + notification.priority + " score=" + sbn.getScore());
    107         pw.println(prefix + "  key=" + sbn.getKey());
    108         pw.println(prefix + "  groupKey=" + getGroupKey());
    109         pw.println(prefix + "  contentIntent=" + notification.contentIntent);
    110         pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
    111         pw.println(prefix + "  tickerText=" + notification.tickerText);
    112         pw.println(prefix + "  contentView=" + notification.contentView);
    113         pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
    114                 notification.defaults, notification.flags));
    115         pw.println(prefix + "  sound=" + notification.sound);
    116         pw.println(prefix + "  audioStreamType=" + notification.audioStreamType);
    117         pw.println(prefix + "  audioAttributes=" + notification.audioAttributes);
    118         pw.println(prefix + String.format("  color=0x%08x", notification.color));
    119         pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
    120         pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
    121                 notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
    122         if (notification.actions != null && notification.actions.length > 0) {
    123             pw.println(prefix + "  actions={");
    124             final int N = notification.actions.length;
    125             for (int i=0; i<N; i++) {
    126                 final Notification.Action action = notification.actions[i];
    127                 pw.println(String.format("%s    [%d] \"%s\" -> %s",
    128                         prefix,
    129                         i,
    130                         action.title,
    131                         action.actionIntent.toString()
    132                         ));
    133             }
    134             pw.println(prefix + "  }");
    135         }
    136         if (notification.extras != null && notification.extras.size() > 0) {
    137             pw.println(prefix + "  extras={");
    138             for (String key : notification.extras.keySet()) {
    139                 pw.print(prefix + "    " + key + "=");
    140                 Object val = notification.extras.get(key);
    141                 if (val == null) {
    142                     pw.println("null");
    143                 } else {
    144                     pw.print(val.getClass().getSimpleName());
    145                     if (val instanceof CharSequence || val instanceof String) {
    146                         // redact contents from bugreports
    147                     } else if (val instanceof Bitmap) {
    148                         pw.print(String.format(" (%dx%d)",
    149                                 ((Bitmap) val).getWidth(),
    150                                 ((Bitmap) val).getHeight()));
    151                     } else if (val.getClass().isArray()) {
    152                         final int N = Array.getLength(val);
    153                         pw.println(" (" + N + ")");
    154                     } else {
    155                         pw.print(" (" + String.valueOf(val) + ")");
    156                     }
    157                     pw.println();
    158                 }
    159             }
    160             pw.println(prefix + "  }");
    161         }
    162         pw.println(prefix + "  stats=" + stats.toString());
    163         pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
    164         pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
    165         pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
    166         pw.println(prefix + "  mPackageVisibility=" + mPackageVisibility);
    167         pw.println(prefix + "  mIntercept=" + mIntercept);
    168         pw.println(prefix + "  mGlobalSortKey=" + mGlobalSortKey);
    169         pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
    170     }
    171 
    172 
    173     static String idDebugString(Context baseContext, String packageName, int id) {
    174         Context c;
    175 
    176         if (packageName != null) {
    177             try {
    178                 c = baseContext.createPackageContext(packageName, 0);
    179             } catch (NameNotFoundException e) {
    180                 c = baseContext;
    181             }
    182         } else {
    183             c = baseContext;
    184         }
    185 
    186         Resources r = c.getResources();
    187         try {
    188             return r.getResourceName(id);
    189         } catch (Resources.NotFoundException e) {
    190             return "<name unknown>";
    191         }
    192     }
    193 
    194     @Override
    195     public final String toString() {
    196         return String.format(
    197                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)",
    198                 System.identityHashCode(this),
    199                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
    200                 this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(),
    201                 this.sbn.getNotification());
    202     }
    203 
    204     public void setContactAffinity(float contactAffinity) {
    205         mContactAffinity = contactAffinity;
    206     }
    207 
    208     public float getContactAffinity() {
    209         return mContactAffinity;
    210     }
    211 
    212     public void setRecentlyIntusive(boolean recentlyIntrusive) {
    213         mRecentlyIntrusive = recentlyIntrusive;
    214     }
    215 
    216     public boolean isRecentlyIntrusive() {
    217         return mRecentlyIntrusive;
    218     }
    219 
    220     public void setPackagePriority(int packagePriority) {
    221         mPackagePriority = packagePriority;
    222     }
    223 
    224     public int getPackagePriority() {
    225         return mPackagePriority;
    226     }
    227 
    228     public void setPackageVisibilityOverride(int packageVisibility) {
    229         mPackageVisibility = packageVisibility;
    230     }
    231 
    232     public int getPackageVisibilityOverride() {
    233         return mPackageVisibility;
    234     }
    235 
    236     public boolean setIntercepted(boolean intercept) {
    237         mIntercept = intercept;
    238         return mIntercept;
    239     }
    240 
    241     public boolean isIntercepted() {
    242         return mIntercept;
    243     }
    244 
    245     public boolean isCategory(String category) {
    246         return Objects.equals(getNotification().category, category);
    247     }
    248 
    249     public boolean isAudioStream(int stream) {
    250         return getNotification().audioStreamType == stream;
    251     }
    252 
    253     public boolean isAudioAttributesUsage(int usage) {
    254         final AudioAttributes attributes = getNotification().audioAttributes;
    255         return attributes != null && attributes.getUsage() == usage;
    256     }
    257 
    258     /**
    259      * Returns the timestamp to use for time-based sorting in the ranker.
    260      */
    261     public long getRankingTimeMs() {
    262         return mRankingTimeMs;
    263     }
    264 
    265     /**
    266      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
    267      *     of the previous notification record, 0 otherwise
    268      */
    269     private long calculateRankingTimeMs(long previousRankingTimeMs) {
    270         Notification n = getNotification();
    271         // Take developer provided 'when', unless it's in the future.
    272         if (n.when != 0 && n.when <= sbn.getPostTime()) {
    273             return n.when;
    274         }
    275         // If we've ranked a previous instance with a timestamp, inherit it. This case is
    276         // important in order to have ranking stability for updating notifications.
    277         if (previousRankingTimeMs > 0) {
    278             return previousRankingTimeMs;
    279         }
    280         return sbn.getPostTime();
    281     }
    282 
    283     public void setGlobalSortKey(String globalSortKey) {
    284         mGlobalSortKey = globalSortKey;
    285     }
    286 
    287     public String getGlobalSortKey() {
    288         return mGlobalSortKey;
    289     }
    290 
    291     public void setAuthoritativeRank(int authoritativeRank) {
    292         mAuthoritativeRank = authoritativeRank;
    293     }
    294 
    295     public int getAuthoritativeRank() {
    296         return mAuthoritativeRank;
    297     }
    298 
    299     public String getGroupKey() {
    300         return sbn.getGroupKey();
    301     }
    302 }
    303