RankingHelper.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.os.Handler; 21import android.os.Message; 22import android.os.UserHandle; 23import android.text.TextUtils; 24import android.util.ArrayMap; 25import android.util.Log; 26import android.util.Slog; 27import android.util.SparseIntArray; 28import org.xmlpull.v1.XmlPullParser; 29import org.xmlpull.v1.XmlPullParserException; 30import org.xmlpull.v1.XmlSerializer; 31 32import java.io.IOException; 33import java.io.PrintWriter; 34import java.util.ArrayList; 35import java.util.Collections; 36import java.util.Comparator; 37import java.util.concurrent.TimeUnit; 38 39public class RankingHelper implements RankingConfig { 40 private static final String TAG = "RankingHelper"; 41 private static final boolean DEBUG = false; 42 43 private static final int XML_VERSION = 1; 44 45 private static final String TAG_RANKING = "ranking"; 46 private static final String TAG_PACKAGE = "package"; 47 private static final String ATT_VERSION = "version"; 48 49 private static final String ATT_NAME = "name"; 50 private static final String ATT_UID = "uid"; 51 private static final String ATT_PRIORITY = "priority"; 52 53 private final NotificationSignalExtractor[] mSignalExtractors; 54 private final NotificationComparator mPreliminaryComparator = new NotificationComparator(); 55 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator(); 56 57 // Package name to uid, to priority. Would be better as Table<String, Int, Int> 58 private final ArrayMap<String, SparseIntArray> mPackagePriorities; 59 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp; 60 61 private final Context mContext; 62 private final Handler mRankingHandler; 63 64 public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) { 65 mContext = context; 66 mRankingHandler = rankingHandler; 67 mPackagePriorities = new ArrayMap<String, SparseIntArray>(); 68 69 final int N = extractorNames.length; 70 mSignalExtractors = new NotificationSignalExtractor[N]; 71 for (int i = 0; i < N; i++) { 72 try { 73 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]); 74 NotificationSignalExtractor extractor = 75 (NotificationSignalExtractor) extractorClass.newInstance(); 76 extractor.initialize(mContext); 77 extractor.setConfig(this); 78 mSignalExtractors[i] = extractor; 79 } catch (ClassNotFoundException e) { 80 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e); 81 } catch (InstantiationException e) { 82 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e); 83 } catch (IllegalAccessException e) { 84 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e); 85 } 86 } 87 mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>(); 88 } 89 90 public void extractSignals(NotificationRecord r) { 91 final int N = mSignalExtractors.length; 92 for (int i = 0; i < N; i++) { 93 NotificationSignalExtractor extractor = mSignalExtractors[i]; 94 try { 95 RankingReconsideration recon = extractor.process(r); 96 if (recon != null) { 97 Message m = Message.obtain(mRankingHandler, 98 NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon); 99 long delay = recon.getDelay(TimeUnit.MILLISECONDS); 100 mRankingHandler.sendMessageDelayed(m, delay); 101 } 102 } catch (Throwable t) { 103 Slog.w(TAG, "NotificationSignalExtractor failed.", t); 104 } 105 } 106 } 107 108 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 109 int type = parser.getEventType(); 110 if (type != XmlPullParser.START_TAG) return; 111 String tag = parser.getName(); 112 if (!TAG_RANKING.equals(tag)) return; 113 mPackagePriorities.clear(); 114 final int version = safeInt(parser, ATT_VERSION, XML_VERSION); 115 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 116 tag = parser.getName(); 117 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { 118 return; 119 } 120 if (type == XmlPullParser.START_TAG) { 121 if (TAG_PACKAGE.equals(tag)) { 122 int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL); 123 int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT); 124 String name = parser.getAttributeValue(null, ATT_NAME); 125 126 if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) { 127 SparseIntArray priorityByUid = mPackagePriorities.get(name); 128 if (priorityByUid == null) { 129 priorityByUid = new SparseIntArray(); 130 mPackagePriorities.put(name, priorityByUid); 131 } 132 priorityByUid.put(uid, priority); 133 } 134 } 135 } 136 } 137 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 138 } 139 140 public void writeXml(XmlSerializer out) throws IOException { 141 out.startTag(null, TAG_RANKING); 142 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); 143 144 final int N = mPackagePriorities.size(); 145 for (int i = 0; i < N; i ++) { 146 String name = mPackagePriorities.keyAt(i); 147 SparseIntArray priorityByUid = mPackagePriorities.get(name); 148 final int M = priorityByUid.size(); 149 for (int j = 0; j < M; j++) { 150 int uid = priorityByUid.keyAt(j); 151 int priority = priorityByUid.get(uid); 152 out.startTag(null, TAG_PACKAGE); 153 out.attribute(null, ATT_NAME, name); 154 out.attribute(null, ATT_UID, Integer.toString(uid)); 155 out.attribute(null, ATT_PRIORITY, Integer.toString(priority)); 156 out.endTag(null, TAG_PACKAGE); 157 } 158 } 159 out.endTag(null, TAG_RANKING); 160 } 161 162 private void updateConfig() { 163 final int N = mSignalExtractors.length; 164 for (int i = 0; i < N; i++) { 165 mSignalExtractors[i].setConfig(this); 166 } 167 mRankingHandler.sendEmptyMessage(NotificationManagerService.MESSAGE_RANKING_CONFIG_CHANGE); 168 } 169 170 public void sort(ArrayList<NotificationRecord> notificationList) { 171 final int N = notificationList.size(); 172 // clear global sort keys 173 for (int i = N - 1; i >= 0; i--) { 174 notificationList.get(i).setGlobalSortKey(null); 175 } 176 177 try { 178 // rank each record individually 179 Collections.sort(notificationList, mPreliminaryComparator); 180 } catch (RuntimeException ex) { 181 // Don't crash the system server if something bad happened. 182 Log.e(TAG, "Extreme badness during notification sort", ex); 183 Log.e(TAG, "Current notification list: "); 184 for (int i = 0; i < N; i++) { 185 NotificationRecord nr = notificationList.get(i); 186 Log.e(TAG, String.format( 187 " [%d] %s (group %s, rank %d, sortkey %s)", 188 i, nr, nr.getGroupKey(), nr.getAuthoritativeRank(), 189 nr.getNotification().getSortKey())); 190 } 191 // STOPSHIP: remove once b/16626175 is found 192 throw ex; 193 } 194 195 synchronized (mProxyByGroupTmp) { 196 // record individual ranking result and nominate proxies for each group 197 for (int i = N - 1; i >= 0; i--) { 198 final NotificationRecord record = notificationList.get(i); 199 record.setAuthoritativeRank(i); 200 final String groupKey = record.getGroupKey(); 201 boolean isGroupSummary = record.getNotification().isGroupSummary(); 202 if (isGroupSummary || !mProxyByGroupTmp.containsKey(groupKey)) { 203 mProxyByGroupTmp.put(groupKey, record); 204 } 205 } 206 // assign global sort key: 207 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank 208 for (int i = 0; i < N; i++) { 209 final NotificationRecord record = notificationList.get(i); 210 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey()); 211 String groupSortKey = record.getNotification().getSortKey(); 212 213 // We need to make sure the developer provided group sort key (gsk) is handled 214 // correctly: 215 // gsk="" < gsk=non-null-string < gsk=null 216 // 217 // We enforce this by using different prefixes for these three cases. 218 String groupSortKeyPortion; 219 if (groupSortKey == null) { 220 groupSortKeyPortion = "nsk"; 221 } else if (groupSortKey.equals("")) { 222 groupSortKeyPortion = "esk"; 223 } else { 224 groupSortKeyPortion = "gsk=" + groupSortKey; 225 } 226 227 boolean isGroupSummary = record.getNotification().isGroupSummary(); 228 record.setGlobalSortKey( 229 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", 230 record.isRecentlyIntrusive() ? '0' : '1', 231 groupProxy.getAuthoritativeRank(), 232 isGroupSummary ? '0' : '1', 233 groupSortKeyPortion, 234 record.getAuthoritativeRank())); 235 } 236 mProxyByGroupTmp.clear(); 237 } 238 239 // Do a second ranking pass, using group proxies 240 Collections.sort(notificationList, mFinalComparator); 241 } 242 243 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) { 244 return Collections.binarySearch(notificationList, target, mFinalComparator); 245 } 246 247 private static int safeInt(XmlPullParser parser, String att, int defValue) { 248 final String val = parser.getAttributeValue(null, att); 249 return tryParseInt(val, defValue); 250 } 251 252 private static int tryParseInt(String value, int defValue) { 253 if (TextUtils.isEmpty(value)) return defValue; 254 try { 255 return Integer.valueOf(value); 256 } catch (NumberFormatException e) { 257 return defValue; 258 } 259 } 260 261 @Override 262 public int getPackagePriority(String packageName, int uid) { 263 int priority = Notification.PRIORITY_DEFAULT; 264 SparseIntArray priorityByUid = mPackagePriorities.get(packageName); 265 if (priorityByUid != null) { 266 priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT); 267 } 268 return priority; 269 } 270 271 @Override 272 public void setPackagePriority(String packageName, int uid, int priority) { 273 if (priority == getPackagePriority(packageName, uid)) { 274 return; 275 } 276 SparseIntArray priorityByUid = mPackagePriorities.get(packageName); 277 if (priorityByUid == null) { 278 priorityByUid = new SparseIntArray(); 279 mPackagePriorities.put(packageName, priorityByUid); 280 } 281 priorityByUid.put(uid, priority); 282 updateConfig(); 283 } 284 285 public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { 286 if (filter == null) { 287 final int N = mSignalExtractors.length; 288 pw.print(prefix); 289 pw.print("mSignalExtractors.length = "); 290 pw.println(N); 291 for (int i = 0; i < N; i++) { 292 pw.print(prefix); 293 pw.print(" "); 294 pw.println(mSignalExtractors[i]); 295 } 296 } 297 final int N = mPackagePriorities.size(); 298 if (filter == null) { 299 pw.print(prefix); 300 pw.println("package priorities:"); 301 } 302 for (int i = 0; i < N; i++) { 303 String name = mPackagePriorities.keyAt(i); 304 if (filter == null || filter.matches(name)) { 305 SparseIntArray priorityByUid = mPackagePriorities.get(name); 306 final int M = priorityByUid.size(); 307 for (int j = 0; j < M; j++) { 308 int uid = priorityByUid.keyAt(j); 309 int priority = priorityByUid.get(uid); 310 pw.print(prefix); 311 pw.print(" "); 312 pw.print(name); 313 pw.print(" ("); 314 pw.print(uid); 315 pw.print(") has priority: "); 316 pw.println(priority); 317 } 318 } 319 } 320 } 321} 322