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