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.graphics.drawable.Icon;
     24 import android.media.AudioAttributes;
     25 import android.os.UserHandle;
     26 import android.service.notification.StatusBarNotification;
     27 
     28 import com.android.internal.annotations.VisibleForTesting;
     29 import com.android.server.EventLogTags;
     30 
     31 import java.io.PrintWriter;
     32 import java.lang.reflect.Array;
     33 import java.util.Arrays;
     34 import java.util.Objects;
     35 
     36 /**
     37  * Holds data about notifications that should not be shared with the
     38  * {@link android.service.notification.NotificationListenerService}s.
     39  *
     40  * <p>These objects should not be mutated unless the code is synchronized
     41  * on {@link NotificationManagerService#mNotificationList}, and any
     42  * modification should be followed by a sorting of that list.</p>
     43  *
     44  * <p>Is sortable by {@link NotificationComparator}.</p>
     45  *
     46  * {@hide}
     47  */
     48 public final class NotificationRecord {
     49     final StatusBarNotification sbn;
     50     final int mOriginalFlags;
     51 
     52     NotificationUsageStats.SingleNotificationStats stats;
     53     boolean isCanceled;
     54     int score;
     55     /** Whether the notification was seen by the user via one of the notification listeners. */
     56     boolean mIsSeen;
     57 
     58     // These members are used by NotificationSignalExtractors
     59     // to communicate with the ranking module.
     60     private float mContactAffinity;
     61     private boolean mRecentlyIntrusive;
     62 
     63     // is this notification currently being intercepted by Zen Mode?
     64     private boolean mIntercept;
     65 
     66     // The timestamp used for ranking.
     67     private long mRankingTimeMs;
     68 
     69     // The first post time, stable across updates.
     70     private long mCreationTimeMs;
     71 
     72     // The most recent visibility event.
     73     private long mVisibleSinceMs;
     74 
     75     // The most recent update time, or the creation time if no updates.
     76     private long mUpdateTimeMs;
     77 
     78     // Is this record an update of an old record?
     79     public boolean isUpdate;
     80     private int mPackagePriority;
     81 
     82     private int mAuthoritativeRank;
     83     private String mGlobalSortKey;
     84     private int mPackageVisibility;
     85 
     86     @VisibleForTesting
     87     public NotificationRecord(StatusBarNotification sbn, int score)
     88     {
     89         this.sbn = sbn;
     90         this.score = score;
     91         mOriginalFlags = sbn.getNotification().flags;
     92         mRankingTimeMs = calculateRankingTimeMs(0L);
     93         mCreationTimeMs = sbn.getPostTime();
     94         mUpdateTimeMs = mCreationTimeMs;
     95     }
     96 
     97     // copy any notes that the ranking system may have made before the update
     98     public void copyRankingInformation(NotificationRecord previous) {
     99         mContactAffinity = previous.mContactAffinity;
    100         mRecentlyIntrusive = previous.mRecentlyIntrusive;
    101         mPackagePriority = previous.mPackagePriority;
    102         mPackageVisibility = previous.mPackageVisibility;
    103         mIntercept = previous.mIntercept;
    104         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
    105         mCreationTimeMs = previous.mCreationTimeMs;
    106         mVisibleSinceMs = previous.mVisibleSinceMs;
    107         // Don't copy mGlobalSortKey, recompute it.
    108     }
    109 
    110     public Notification getNotification() { return sbn.getNotification(); }
    111     public int getFlags() { return sbn.getNotification().flags; }
    112     public UserHandle getUser() { return sbn.getUser(); }
    113     public String getKey() { return sbn.getKey(); }
    114     /** @deprecated Use {@link #getUser()} instead. */
    115     public int getUserId() { return sbn.getUserId(); }
    116 
    117     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
    118         final Notification notification = sbn.getNotification();
    119         final Icon icon = notification.getSmallIcon();
    120         String iconStr = String.valueOf(icon);
    121         if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
    122             iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
    123         }
    124         pw.println(prefix + this);
    125         pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
    126         pw.println(prefix + "  icon=" + iconStr);
    127         pw.println(prefix + "  pri=" + notification.priority + " score=" + sbn.getScore());
    128         pw.println(prefix + "  key=" + sbn.getKey());
    129         pw.println(prefix + "  seen=" + mIsSeen);
    130         pw.println(prefix + "  groupKey=" + getGroupKey());
    131         pw.println(prefix + "  contentIntent=" + notification.contentIntent);
    132         pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
    133         pw.println(prefix + "  tickerText=" + notification.tickerText);
    134         pw.println(prefix + "  contentView=" + notification.contentView);
    135         pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
    136                 notification.defaults, notification.flags));
    137         pw.println(prefix + "  sound=" + notification.sound);
    138         pw.println(prefix + "  audioStreamType=" + notification.audioStreamType);
    139         pw.println(prefix + "  audioAttributes=" + notification.audioAttributes);
    140         pw.println(prefix + String.format("  color=0x%08x", notification.color));
    141         pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
    142         pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
    143                 notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
    144         if (notification.actions != null && notification.actions.length > 0) {
    145             pw.println(prefix + "  actions={");
    146             final int N = notification.actions.length;
    147             for (int i=0; i<N; i++) {
    148                 final Notification.Action action = notification.actions[i];
    149                 pw.println(String.format("%s    [%d] \"%s\" -> %s",
    150                         prefix,
    151                         i,
    152                         action.title,
    153                         action.actionIntent.toString()
    154                         ));
    155             }
    156             pw.println(prefix + "  }");
    157         }
    158         if (notification.extras != null && notification.extras.size() > 0) {
    159             pw.println(prefix + "  extras={");
    160             for (String key : notification.extras.keySet()) {
    161                 pw.print(prefix + "    " + key + "=");
    162                 Object val = notification.extras.get(key);
    163                 if (val == null) {
    164                     pw.println("null");
    165                 } else {
    166                     pw.print(val.getClass().getSimpleName());
    167                     if (redact && (val instanceof CharSequence || val instanceof String)) {
    168                         // redact contents from bugreports
    169                     } else if (val instanceof Bitmap) {
    170                         pw.print(String.format(" (%dx%d)",
    171                                 ((Bitmap) val).getWidth(),
    172                                 ((Bitmap) val).getHeight()));
    173                     } else if (val.getClass().isArray()) {
    174                         final int N = Array.getLength(val);
    175                         pw.print(" (" + N + ")");
    176                         if (!redact) {
    177                             for (int j=0; j<N; j++) {
    178                                 pw.println();
    179                                 pw.print(String.format("%s      [%d] %s",
    180                                         prefix, j, String.valueOf(Array.get(val, j))));
    181                             }
    182                         }
    183                     } else {
    184                         pw.print(" (" + String.valueOf(val) + ")");
    185                     }
    186                     pw.println();
    187                 }
    188             }
    189             pw.println(prefix + "  }");
    190         }
    191         pw.println(prefix + "  stats=" + stats.toString());
    192         pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
    193         pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
    194         pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
    195         pw.println(prefix + "  mPackageVisibility=" + mPackageVisibility);
    196         pw.println(prefix + "  mIntercept=" + mIntercept);
    197         pw.println(prefix + "  mGlobalSortKey=" + mGlobalSortKey);
    198         pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
    199         pw.println(prefix + "  mCreationTimeMs=" + mCreationTimeMs);
    200         pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
    201         pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
    202     }
    203 
    204 
    205     static String idDebugString(Context baseContext, String packageName, int id) {
    206         Context c;
    207 
    208         if (packageName != null) {
    209             try {
    210                 c = baseContext.createPackageContext(packageName, 0);
    211             } catch (NameNotFoundException e) {
    212                 c = baseContext;
    213             }
    214         } else {
    215             c = baseContext;
    216         }
    217 
    218         Resources r = c.getResources();
    219         try {
    220             return r.getResourceName(id);
    221         } catch (Resources.NotFoundException e) {
    222             return "<name unknown>";
    223         }
    224     }
    225 
    226     @Override
    227     public final String toString() {
    228         return String.format(
    229                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)",
    230                 System.identityHashCode(this),
    231                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
    232                 this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(),
    233                 this.sbn.getNotification());
    234     }
    235 
    236     public void setContactAffinity(float contactAffinity) {
    237         mContactAffinity = contactAffinity;
    238     }
    239 
    240     public float getContactAffinity() {
    241         return mContactAffinity;
    242     }
    243 
    244     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
    245         mRecentlyIntrusive = recentlyIntrusive;
    246     }
    247 
    248     public boolean isRecentlyIntrusive() {
    249         return mRecentlyIntrusive;
    250     }
    251 
    252     public void setPackagePriority(int packagePriority) {
    253         mPackagePriority = packagePriority;
    254     }
    255 
    256     public int getPackagePriority() {
    257         return mPackagePriority;
    258     }
    259 
    260     public void setPackageVisibilityOverride(int packageVisibility) {
    261         mPackageVisibility = packageVisibility;
    262     }
    263 
    264     public int getPackageVisibilityOverride() {
    265         return mPackageVisibility;
    266     }
    267 
    268     public boolean setIntercepted(boolean intercept) {
    269         mIntercept = intercept;
    270         return mIntercept;
    271     }
    272 
    273     public boolean isIntercepted() {
    274         return mIntercept;
    275     }
    276 
    277     public boolean isCategory(String category) {
    278         return Objects.equals(getNotification().category, category);
    279     }
    280 
    281     public boolean isAudioStream(int stream) {
    282         return getNotification().audioStreamType == stream;
    283     }
    284 
    285     public boolean isAudioAttributesUsage(int usage) {
    286         final AudioAttributes attributes = getNotification().audioAttributes;
    287         return attributes != null && attributes.getUsage() == usage;
    288     }
    289 
    290     /**
    291      * Returns the timestamp to use for time-based sorting in the ranker.
    292      */
    293     public long getRankingTimeMs() {
    294         return mRankingTimeMs;
    295     }
    296 
    297     /**
    298      * @param now this current time in milliseconds.
    299      * @returns the number of milliseconds since the most recent update, or the post time if none.
    300      */
    301     public int getFreshnessMs(long now) {
    302         return (int) (now - mUpdateTimeMs);
    303     }
    304 
    305     /**
    306      * @param now this current time in milliseconds.
    307      * @returns the number of milliseconds since the the first post, ignoring updates.
    308      */
    309     public int getLifespanMs(long now) {
    310         return (int) (now - mCreationTimeMs);
    311     }
    312 
    313     /**
    314      * @param now this current time in milliseconds.
    315      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
    316      */
    317     public int getExposureMs(long now) {
    318         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
    319     }
    320 
    321     /**
    322      * Set the visibility of the notification.
    323      */
    324     public void setVisibility(boolean visible, int rank) {
    325         final long now = System.currentTimeMillis();
    326         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
    327         stats.onVisibilityChanged(visible);
    328         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
    329                 (int) (now - mCreationTimeMs),
    330                 (int) (now - mUpdateTimeMs),
    331                 0, // exposure time
    332                 rank);
    333     }
    334 
    335     /**
    336      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
    337      *     of the previous notification record, 0 otherwise
    338      */
    339     private long calculateRankingTimeMs(long previousRankingTimeMs) {
    340         Notification n = getNotification();
    341         // Take developer provided 'when', unless it's in the future.
    342         if (n.when != 0 && n.when <= sbn.getPostTime()) {
    343             return n.when;
    344         }
    345         // If we've ranked a previous instance with a timestamp, inherit it. This case is
    346         // important in order to have ranking stability for updating notifications.
    347         if (previousRankingTimeMs > 0) {
    348             return previousRankingTimeMs;
    349         }
    350         return sbn.getPostTime();
    351     }
    352 
    353     public void setGlobalSortKey(String globalSortKey) {
    354         mGlobalSortKey = globalSortKey;
    355     }
    356 
    357     public String getGlobalSortKey() {
    358         return mGlobalSortKey;
    359     }
    360 
    361     /** Check if any of the listeners have marked this notification as seen by the user. */
    362     public boolean isSeen() {
    363         return mIsSeen;
    364     }
    365 
    366     /** Mark the notification as seen by the user. */
    367     public void setSeen() {
    368         mIsSeen = true;
    369     }
    370 
    371     public void setAuthoritativeRank(int authoritativeRank) {
    372         mAuthoritativeRank = authoritativeRank;
    373     }
    374 
    375     public int getAuthoritativeRank() {
    376         return mAuthoritativeRank;
    377     }
    378 
    379     public String getGroupKey() {
    380         return sbn.getGroupKey();
    381     }
    382 }
    383