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