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