RankingHelper.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.net.Uri; 21import android.os.Handler; 22import android.os.Message; 23import android.os.UserHandle; 24import android.text.TextUtils; 25import android.util.ArrayMap; 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.concurrent.TimeUnit; 37 38public class RankingHelper implements RankingConfig { 39 private static final String TAG = "RankingHelper"; 40 private static final boolean DEBUG = false; 41 42 private static final int XML_VERSION = 1; 43 44 private static final String TAG_RANKING = "ranking"; 45 private static final String TAG_PACKAGE = "package"; 46 private static final String ATT_VERSION = "version"; 47 48 private static final String ATT_NAME = "name"; 49 private static final String ATT_UID = "uid"; 50 private static final String ATT_PRIORITY = "priority"; 51 52 private final NotificationSignalExtractor[] mSignalExtractors; 53 private final NotificationComparator mPreliminaryComparator = new NotificationComparator(); 54 private final NotificationComparator mFinalComparator = new GroupedNotificationComparator(); 55 56 // Package name to uid, to priority. Would be better as Table<String, Int, Int> 57 private final ArrayMap<String, SparseIntArray> mPackagePriorities; 58 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp; 59 60 private final Context mContext; 61 private final Handler mRankingHandler; 62 63 public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) { 64 mContext = context; 65 mRankingHandler = rankingHandler; 66 mPackagePriorities = new ArrayMap<String, SparseIntArray>(); 67 68 final int N = extractorNames.length; 69 mSignalExtractors = new NotificationSignalExtractor[N]; 70 for (int i = 0; i < N; i++) { 71 try { 72 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]); 73 NotificationSignalExtractor extractor = 74 (NotificationSignalExtractor) extractorClass.newInstance(); 75 extractor.initialize(mContext); 76 extractor.setConfig(this); 77 mSignalExtractors[i] = extractor; 78 } catch (ClassNotFoundException e) { 79 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e); 80 } catch (InstantiationException e) { 81 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e); 82 } catch (IllegalAccessException e) { 83 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e); 84 } 85 } 86 mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>(); 87 } 88 89 public void extractSignals(NotificationRecord r) { 90 final int N = mSignalExtractors.length; 91 for (int i = 0; i < N; i++) { 92 NotificationSignalExtractor extractor = mSignalExtractors[i]; 93 try { 94 RankingReconsideration recon = extractor.process(r); 95 if (recon != null) { 96 Message m = Message.obtain(mRankingHandler, 97 NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon); 98 long delay = recon.getDelay(TimeUnit.MILLISECONDS); 99 mRankingHandler.sendMessageDelayed(m, delay); 100 } 101 } catch (Throwable t) { 102 Slog.w(TAG, "NotificationSignalExtractor failed.", t); 103 } 104 } 105 } 106 107 public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { 108 int type = parser.getEventType(); 109 if (type != XmlPullParser.START_TAG) return; 110 String tag = parser.getName(); 111 if (!TAG_RANKING.equals(tag)) return; 112 mPackagePriorities.clear(); 113 final int version = safeInt(parser, ATT_VERSION, XML_VERSION); 114 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 115 tag = parser.getName(); 116 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { 117 return; 118 } 119 if (type == XmlPullParser.START_TAG) { 120 if (TAG_PACKAGE.equals(tag)) { 121 int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL); 122 int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT); 123 String name = parser.getAttributeValue(null, ATT_NAME); 124 125 if (!TextUtils.isEmpty(name) && priority != Notification.PRIORITY_DEFAULT) { 126 SparseIntArray priorityByUid = mPackagePriorities.get(name); 127 if (priorityByUid == null) { 128 priorityByUid = new SparseIntArray(); 129 mPackagePriorities.put(name, priorityByUid); 130 } 131 priorityByUid.put(uid, priority); 132 } 133 } 134 } 135 } 136 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 137 } 138 139 public void writeXml(XmlSerializer out) throws IOException { 140 out.startTag(null, TAG_RANKING); 141 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); 142 143 final int N = mPackagePriorities.size(); 144 for (int i = 0; i < N; i ++) { 145 String name = mPackagePriorities.keyAt(i); 146 SparseIntArray priorityByUid = mPackagePriorities.get(name); 147 final int M = priorityByUid.size(); 148 for (int j = 0; j < M; j++) { 149 int uid = priorityByUid.keyAt(j); 150 int priority = priorityByUid.get(uid); 151 out.startTag(null, TAG_PACKAGE); 152 out.attribute(null, ATT_NAME, name); 153 out.attribute(null, ATT_UID, Integer.toString(uid)); 154 out.attribute(null, ATT_PRIORITY, Integer.toString(priority)); 155 out.endTag(null, TAG_PACKAGE); 156 } 157 } 158 out.endTag(null, TAG_RANKING); 159 } 160 161 private void updateConfig() { 162 final int N = mSignalExtractors.length; 163 for (int i = 0; i < N; i++) { 164 mSignalExtractors[i].setConfig(this); 165 } 166 mRankingHandler.sendEmptyMessage(NotificationManagerService.MESSAGE_RANKING_CONFIG_CHANGE); 167 } 168 169 public void sort(ArrayList<NotificationRecord> notificationList) { 170 final int N = notificationList.size(); 171 // clear group proxies 172 for (int i = N - 1; i >= 0; i--) { 173 notificationList.get(i).setRankingProxy(null); 174 } 175 176 // rank each record individually 177 Collections.sort(notificationList, mPreliminaryComparator); 178 179 // record inidivdual ranking result and nominate proxies for each group 180 for (int i = N - 1; i >= 0; i--) { 181 final NotificationRecord record = notificationList.get(i); 182 record.setAuthoritativeRank(i); 183 final String groupKey = record.getGroupKey(); 184 boolean isGroupSummary = record.getNotification().getGroup() != null 185 && (record.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0; 186 if (isGroupSummary || mProxyByGroupTmp.get(groupKey) == null) { 187 mProxyByGroupTmp.put(groupKey, record); 188 } 189 } 190 // assign nominated proxies to each notification 191 for (int i = 0; i < N; i++) { 192 final NotificationRecord record = notificationList.get(i); 193 record.setRankingProxy(mProxyByGroupTmp.get(record.getGroupKey())); 194 } 195 // Do a second ranking pass, using group proxies 196 Collections.sort(notificationList, mFinalComparator); 197 198 mProxyByGroupTmp.clear(); 199 } 200 201 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) { 202 return Collections.binarySearch(notificationList, target, mFinalComparator); 203 } 204 205 private static int safeInt(XmlPullParser parser, String att, int defValue) { 206 final String val = parser.getAttributeValue(null, att); 207 return tryParseInt(val, defValue); 208 } 209 210 private static int tryParseInt(String value, int defValue) { 211 if (TextUtils.isEmpty(value)) return defValue; 212 try { 213 return Integer.valueOf(value); 214 } catch (NumberFormatException e) { 215 return defValue; 216 } 217 } 218 219 @Override 220 public int getPackagePriority(String packageName, int uid) { 221 int priority = Notification.PRIORITY_DEFAULT; 222 SparseIntArray priorityByUid = mPackagePriorities.get(packageName); 223 if (priorityByUid != null) { 224 priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT); 225 } 226 return priority; 227 } 228 229 @Override 230 public void setPackagePriority(String packageName, int uid, int priority) { 231 if (priority == getPackagePriority(packageName, uid)) { 232 return; 233 } 234 SparseIntArray priorityByUid = mPackagePriorities.get(packageName); 235 if (priorityByUid == null) { 236 priorityByUid = new SparseIntArray(); 237 mPackagePriorities.put(packageName, priorityByUid); 238 } 239 priorityByUid.put(uid, priority); 240 updateConfig(); 241 } 242 243 public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { 244 if (filter == null) { 245 final int N = mSignalExtractors.length; 246 pw.print(prefix); 247 pw.print("mSignalExtractors.length = "); 248 pw.println(N); 249 for (int i = 0; i < N; i++) { 250 pw.print(prefix); 251 pw.print(" "); 252 pw.println(mSignalExtractors[i]); 253 } 254 } 255 final int N = mPackagePriorities.size(); 256 if (filter == null) { 257 pw.print(prefix); 258 pw.println("package priorities:"); 259 } 260 for (int i = 0; i < N; i++) { 261 String name = mPackagePriorities.keyAt(i); 262 if (filter == null || filter.matches(name)) { 263 SparseIntArray priorityByUid = mPackagePriorities.get(name); 264 final int M = priorityByUid.size(); 265 for (int j = 0; j < M; j++) { 266 int uid = priorityByUid.keyAt(j); 267 int priority = priorityByUid.get(uid); 268 pw.print(prefix); 269 pw.print(" "); 270 pw.print(name); 271 pw.print(" ("); 272 pw.print(uid); 273 pw.print(") has priority: "); 274 pw.println(priority); 275 } 276 } 277 } 278 } 279} 280