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