RankingHelper.java revision 03533715295154f0bb66f84b7c25832d21156805
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 static android.app.NotificationManager.IMPORTANCE_NONE; 19 20import com.android.internal.R; 21import com.android.internal.util.Preconditions; 22 23import android.app.Notification; 24import android.app.NotificationChannel; 25import android.app.NotificationManager; 26import android.content.Context; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.PackageManager.NameNotFoundException; 30import android.content.pm.ParceledListSlice; 31import android.os.Build; 32import android.os.Process; 33import android.os.UserHandle; 34import android.service.notification.NotificationListenerService.Ranking; 35import android.text.TextUtils; 36import android.util.ArrayMap; 37import android.util.Slog; 38 39import org.json.JSONArray; 40import org.json.JSONException; 41import org.json.JSONObject; 42import org.xmlpull.v1.XmlPullParser; 43import org.xmlpull.v1.XmlPullParserException; 44import org.xmlpull.v1.XmlSerializer; 45 46import java.io.IOException; 47import java.io.PrintWriter; 48import java.util.ArrayList; 49import java.util.Collections; 50import java.util.List; 51import java.util.Map; 52import java.util.Map.Entry; 53 54public class RankingHelper implements RankingConfig { 55 private static final String TAG = "RankingHelper"; 56 57 private static final int XML_VERSION = 1; 58 59 private static final String TAG_RANKING = "ranking"; 60 private static final String TAG_PACKAGE = "package"; 61 private static final String TAG_CHANNEL = "channel"; 62 63 private static final String ATT_VERSION = "version"; 64 private static final String ATT_NAME = "name"; 65 private static final String ATT_UID = "uid"; 66 private static final String ATT_ID = "id"; 67 private static final String ATT_PRIORITY = "priority"; 68 private static final String ATT_VISIBILITY = "visibility"; 69 private static final String ATT_IMPORTANCE = "importance"; 70 71 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; 72 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; 73 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; 74 75 private final NotificationSignalExtractor[] mSignalExtractors; 76 private final NotificationComparator mPreliminaryComparator; 77 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator(); 78 79 private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record 80 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>(); 81 private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record 82 83 private final Context mContext; 84 private final RankingHandler mRankingHandler; 85 private final PackageManager mPm; 86 87 public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler, 88 NotificationUsageStats usageStats, String[] extractorNames) { 89 mContext = context; 90 mRankingHandler = rankingHandler; 91 mPm = pm; 92 93 mPreliminaryComparator = new NotificationComparator(mContext); 94 95 final int N = extractorNames.length; 96 mSignalExtractors = new NotificationSignalExtractor[N]; 97 for (int i = 0; i < N; i++) { 98 try { 99 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]); 100 NotificationSignalExtractor extractor = 101 (NotificationSignalExtractor) extractorClass.newInstance(); 102 extractor.initialize(mContext, usageStats); 103 extractor.setConfig(this); 104 mSignalExtractors[i] = extractor; 105 } catch (ClassNotFoundException e) { 106 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e); 107 } catch (InstantiationException e) { 108 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e); 109 } catch (IllegalAccessException e) { 110 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e); 111 } 112 } 113 } 114 115 @SuppressWarnings("unchecked") 116 public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) { 117 final int N = mSignalExtractors.length; 118 for (int i = 0; i < N; i++) { 119 final NotificationSignalExtractor extractor = mSignalExtractors[i]; 120 if (extractorClass.equals(extractor.getClass())) { 121 return (T) extractor; 122 } 123 } 124 return null; 125 } 126 127 public void extractSignals(NotificationRecord r) { 128 final int N = mSignalExtractors.length; 129 for (int i = 0; i < N; i++) { 130 NotificationSignalExtractor extractor = mSignalExtractors[i]; 131 try { 132 RankingReconsideration recon = extractor.process(r); 133 if (recon != null) { 134 mRankingHandler.requestReconsideration(recon); 135 } 136 } catch (Throwable t) { 137 Slog.w(TAG, "NotificationSignalExtractor failed.", t); 138 } 139 } 140 } 141 142 public void readXml(XmlPullParser parser, boolean forRestore) 143 throws XmlPullParserException, IOException { 144 int type = parser.getEventType(); 145 if (type != XmlPullParser.START_TAG) return; 146 String tag = parser.getName(); 147 if (!TAG_RANKING.equals(tag)) return; 148 mRecords.clear(); 149 mRestoredWithoutUids.clear(); 150 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 151 tag = parser.getName(); 152 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { 153 return; 154 } 155 if (type == XmlPullParser.START_TAG) { 156 if (TAG_PACKAGE.equals(tag)) { 157 int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID); 158 String name = parser.getAttributeValue(null, ATT_NAME); 159 if (!TextUtils.isEmpty(name)) { 160 if (forRestore) { 161 try { 162 //TODO: http://b/22388012 163 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM); 164 } catch (NameNotFoundException e) { 165 // noop 166 } 167 } 168 169 Record r = getOrCreateRecord(name, uid, 170 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE), 171 safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY), 172 safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY)); 173 174 // Channels 175 final int innerDepth = parser.getDepth(); 176 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 177 && (type != XmlPullParser.END_TAG 178 || parser.getDepth() > innerDepth)) { 179 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 180 continue; 181 } 182 183 String tagName = parser.getName(); 184 if (TAG_CHANNEL.equals(tagName)) { 185 String id = parser.getAttributeValue(null, ATT_ID); 186 CharSequence channelName = parser.getAttributeValue(null, ATT_NAME); 187 int channelImportance = 188 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); 189 190 if (!TextUtils.isEmpty(id)) { 191 final NotificationChannel channel = new NotificationChannel(id, 192 channelName, channelImportance); 193 channel.populateFromXml(parser); 194 r.channels.put(id, channel); 195 } 196 } 197 } 198 199 clampDefaultChannel(r); 200 } 201 } 202 } 203 } 204 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 205 } 206 207 private static String recordKey(String pkg, int uid) { 208 return pkg + "|" + uid; 209 } 210 211 private Record getRecord(String pkg, int uid) { 212 final String key = recordKey(pkg, uid); 213 return mRecords.get(key); 214 } 215 216 private Record getOrCreateRecord(String pkg, int uid) { 217 return getOrCreateRecord(pkg, uid, 218 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY); 219 } 220 221 private Record getOrCreateRecord(String pkg, int uid, int importance, int priority, 222 int visibility) { 223 final String key = recordKey(pkg, uid); 224 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(key); 225 if (r == null) { 226 r = new Record(); 227 r.pkg = pkg; 228 r.uid = uid; 229 r.importance = importance; 230 r.priority = priority; 231 r.visibility = visibility; 232 createDefaultChannelIfMissing(r); 233 if (r.uid == Record.UNKNOWN_UID) { 234 mRestoredWithoutUids.put(pkg, r); 235 } else { 236 mRecords.put(key, r); 237 } 238 clampDefaultChannel(r); 239 } 240 return r; 241 } 242 243 // Clamp the importance level of the default channel for apps targeting the new SDK version, 244 // unless the user has already changed the importance. 245 private void clampDefaultChannel(Record r) { 246 try { 247 if (r.uid != Record.UNKNOWN_UID) { 248 int userId = UserHandle.getUserId(r.uid); 249 final ApplicationInfo applicationInfo = 250 mPm.getApplicationInfoAsUser(r.pkg, 0, userId); 251 if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) { 252 final NotificationChannel defaultChannel = 253 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID); 254 if ((defaultChannel.getUserLockedFields() 255 & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) { 256 defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW); 257 updateConfig(); 258 } 259 } 260 } 261 } catch (NameNotFoundException e) { 262 // oh well. 263 } 264 } 265 266 private void createDefaultChannelIfMissing(Record r) { 267 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 268 NotificationChannel channel; 269 channel = new NotificationChannel( 270 NotificationChannel.DEFAULT_CHANNEL_ID, 271 mContext.getString(R.string.default_notification_channel_label), 272 r.importance); 273 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); 274 channel.setLockscreenVisibility(r.visibility); 275 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) { 276 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 277 } 278 if (r.priority != DEFAULT_PRIORITY) { 279 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); 280 } 281 if (r.visibility != DEFAULT_VISIBILITY) { 282 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); 283 } 284 r.channels.put(channel.getId(), channel); 285 } 286 } 287 288 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException { 289 out.startTag(null, TAG_RANKING); 290 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); 291 292 final int N = mRecords.size(); 293 for (int i = 0; i < N; i++) { 294 final Record r = mRecords.valueAt(i); 295 //TODO: http://b/22388012 296 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) { 297 continue; 298 } 299 final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE 300 || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY 301 || r.channels.size() > 0; 302 if (hasNonDefaultSettings) { 303 out.startTag(null, TAG_PACKAGE); 304 out.attribute(null, ATT_NAME, r.pkg); 305 if (r.importance != DEFAULT_IMPORTANCE) { 306 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance)); 307 } 308 if (r.priority != DEFAULT_PRIORITY) { 309 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority)); 310 } 311 if (r.visibility != DEFAULT_VISIBILITY) { 312 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); 313 } 314 315 if (!forBackup) { 316 out.attribute(null, ATT_UID, Integer.toString(r.uid)); 317 } 318 319 for (NotificationChannel channel : r.channels.values()) { 320 channel.writeXml(out); 321 } 322 323 out.endTag(null, TAG_PACKAGE); 324 } 325 } 326 out.endTag(null, TAG_RANKING); 327 } 328 329 private void updateConfig() { 330 final int N = mSignalExtractors.length; 331 for (int i = 0; i < N; i++) { 332 mSignalExtractors[i].setConfig(this); 333 } 334 mRankingHandler.requestSort(false); 335 } 336 337 public void sort(ArrayList<NotificationRecord> notificationList) { 338 final int N = notificationList.size(); 339 // clear global sort keys 340 for (int i = N - 1; i >= 0; i--) { 341 notificationList.get(i).setGlobalSortKey(null); 342 } 343 344 // rank each record individually 345 Collections.sort(notificationList, mPreliminaryComparator); 346 347 synchronized (mProxyByGroupTmp) { 348 // record individual ranking result and nominate proxies for each group 349 for (int i = N - 1; i >= 0; i--) { 350 final NotificationRecord record = notificationList.get(i); 351 record.setAuthoritativeRank(i); 352 final String groupKey = record.getGroupKey(); 353 boolean isGroupSummary = record.getNotification().isGroupSummary(); 354 if (isGroupSummary || !mProxyByGroupTmp.containsKey(groupKey)) { 355 mProxyByGroupTmp.put(groupKey, record); 356 } 357 } 358 // assign global sort key: 359 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank 360 for (int i = 0; i < N; i++) { 361 final NotificationRecord record = notificationList.get(i); 362 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey()); 363 String groupSortKey = record.getNotification().getSortKey(); 364 365 // We need to make sure the developer provided group sort key (gsk) is handled 366 // correctly: 367 // gsk="" < gsk=non-null-string < gsk=null 368 // 369 // We enforce this by using different prefixes for these three cases. 370 String groupSortKeyPortion; 371 if (groupSortKey == null) { 372 groupSortKeyPortion = "nsk"; 373 } else if (groupSortKey.equals("")) { 374 groupSortKeyPortion = "esk"; 375 } else { 376 groupSortKeyPortion = "gsk=" + groupSortKey; 377 } 378 379 boolean isGroupSummary = record.getNotification().isGroupSummary(); 380 record.setGlobalSortKey( 381 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", 382 record.isRecentlyIntrusive() ? '0' : '1', 383 groupProxy.getAuthoritativeRank(), 384 isGroupSummary ? '0' : '1', 385 groupSortKeyPortion, 386 record.getAuthoritativeRank())); 387 } 388 mProxyByGroupTmp.clear(); 389 } 390 391 // Do a second ranking pass, using group proxies 392 Collections.sort(notificationList, mFinalComparator); 393 } 394 395 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) { 396 return Collections.binarySearch(notificationList, target, mFinalComparator); 397 } 398 399 private static int safeInt(XmlPullParser parser, String att, int defValue) { 400 final String val = parser.getAttributeValue(null, att); 401 return tryParseInt(val, defValue); 402 } 403 404 private static int tryParseInt(String value, int defValue) { 405 if (TextUtils.isEmpty(value)) return defValue; 406 try { 407 return Integer.parseInt(value); 408 } catch (NumberFormatException e) { 409 return defValue; 410 } 411 } 412 413 /** 414 * Gets importance. 415 */ 416 @Override 417 public int getImportance(String packageName, int uid) { 418 return getOrCreateRecord(packageName, uid).importance; 419 } 420 421 @Override 422 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel, 423 boolean fromTargetApp) { 424 Preconditions.checkNotNull(pkg); 425 Preconditions.checkNotNull(channel); 426 Preconditions.checkNotNull(channel.getId()); 427 Preconditions.checkNotNull(channel.getName()); 428 Record r = getOrCreateRecord(pkg, uid); 429 if (r == null) { 430 throw new IllegalArgumentException("Invalid package"); 431 } 432 if (IMPORTANCE_NONE == r.importance) { 433 throw new IllegalArgumentException("Package blocked"); 434 } 435 if (r.channels.containsKey(channel.getId()) || channel.getName().equals( 436 mContext.getString(R.string.default_notification_channel_label))) { 437 // Channel already exists, no-op. 438 return; 439 } 440 if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE 441 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) { 442 throw new IllegalArgumentException("Invalid importance level"); 443 } 444 // Reset fields that apps aren't allowed to set. 445 if (fromTargetApp) { 446 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); 447 channel.setLockscreenVisibility(r.visibility); 448 } 449 channel.setAllowed(true); 450 clearLockedFields(channel); 451 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 452 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 453 } 454 r.channels.put(channel.getId(), channel); 455 updateConfig(); 456 } 457 458 private void clearLockedFields(NotificationChannel channel) { 459 int clearMask = 0; 460 for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) { 461 clearMask |= NotificationChannel.LOCKABLE_FIELDS[i]; 462 } 463 channel.lockFields(~clearMask); 464 } 465 466 @Override 467 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) { 468 Preconditions.checkNotNull(updatedChannel); 469 Preconditions.checkNotNull(updatedChannel.getId()); 470 Record r = getOrCreateRecord(pkg, uid); 471 if (r == null) { 472 throw new IllegalArgumentException("Invalid package"); 473 } 474 NotificationChannel channel = r.channels.get(updatedChannel.getId()); 475 if (channel == null) { 476 throw new IllegalArgumentException("Channel does not exist"); 477 } 478 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 479 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 480 } 481 r.channels.put(updatedChannel.getId(), updatedChannel); 482 updateConfig(); 483 } 484 485 @Override 486 public void updateNotificationChannelFromAssistant(String pkg, int uid, 487 NotificationChannel updatedChannel) { 488 Record r = getOrCreateRecord(pkg, uid); 489 if (r == null) { 490 throw new IllegalArgumentException("Invalid package"); 491 } 492 NotificationChannel channel = r.channels.get(updatedChannel.getId()); 493 if (channel == null) { 494 throw new IllegalArgumentException("Channel does not exist"); 495 } 496 497 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) { 498 channel.setImportance(updatedChannel.getImportance()); 499 } 500 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { 501 channel.setLights(updatedChannel.shouldShowLights()); 502 } 503 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0) { 504 channel.setBypassDnd(updatedChannel.canBypassDnd()); 505 } 506 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) { 507 channel.setSound(updatedChannel.getSound()); 508 } 509 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { 510 channel.enableVibration(updatedChannel.shouldVibrate()); 511 channel.setVibrationPattern(updatedChannel.getVibrationPattern()); 512 } 513 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0) { 514 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 515 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 516 } else { 517 channel.setLockscreenVisibility(updatedChannel.getLockscreenVisibility()); 518 } 519 } 520 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_ALLOWED) == 0) { 521 channel.setAllowed(updatedChannel.isAllowed()); 522 } 523 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0) { 524 channel.setShowBadge(updatedChannel.canShowBadge()); 525 } 526 527 r.channels.put(channel.getId(), channel); 528 updateConfig(); 529 } 530 531 @Override 532 public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, 533 String channelId) { 534 Record r = getOrCreateRecord(pkg, uid); 535 if (channelId == null) { 536 channelId = NotificationChannel.DEFAULT_CHANNEL_ID; 537 } 538 NotificationChannel channel = r.channels.get(channelId); 539 if (channel != null) { 540 return channel; 541 } else { 542 return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID); 543 } 544 } 545 546 @Override 547 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) { 548 Preconditions.checkNotNull(pkg); 549 Record r = getOrCreateRecord(pkg, uid); 550 if (r == null) { 551 throw new IllegalArgumentException("Invalid package"); 552 } 553 if (channelId == null) { 554 channelId = NotificationChannel.DEFAULT_CHANNEL_ID; 555 } 556 return r.channels.get(channelId); 557 } 558 559 @Override 560 public void deleteNotificationChannel(String pkg, int uid, String channelId) { 561 Preconditions.checkNotNull(pkg); 562 Preconditions.checkNotNull(channelId); 563 Record r = getRecord(pkg, uid); 564 if (r == null) { 565 throw new IllegalArgumentException("Invalid package"); 566 } 567 if (r != null) { 568 r.channels.remove(channelId); 569 } 570 } 571 572 @Override 573 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid) { 574 Preconditions.checkNotNull(pkg); 575 List<NotificationChannel> channels = new ArrayList<>(); 576 Record r = getRecord(pkg, uid); 577 if (r == null) { 578 throw new IllegalArgumentException("Invalid package"); 579 } 580 int N = r.channels.size(); 581 for (int i = 0; i < N; i++) { 582 channels.add(r.channels.valueAt(i)); 583 } 584 return new ParceledListSlice<>(channels); 585 } 586 587 /** 588 * Sets importance. 589 */ 590 @Override 591 public void setImportance(String pkgName, int uid, int importance) { 592 getOrCreateRecord(pkgName, uid).importance = importance; 593 updateConfig(); 594 } 595 596 public void setEnabled(String packageName, int uid, boolean enabled) { 597 boolean wasEnabled = getImportance(packageName, uid) != NotificationManager.IMPORTANCE_NONE; 598 if (wasEnabled == enabled) { 599 return; 600 } 601 setImportance(packageName, uid, 602 enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE); 603 } 604 605 public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { 606 if (filter == null) { 607 final int N = mSignalExtractors.length; 608 pw.print(prefix); 609 pw.print("mSignalExtractors.length = "); 610 pw.println(N); 611 for (int i = 0; i < N; i++) { 612 pw.print(prefix); 613 pw.print(" "); 614 pw.println(mSignalExtractors[i]); 615 } 616 } 617 if (filter == null) { 618 pw.print(prefix); 619 pw.println("per-package config:"); 620 } 621 pw.println("Records:"); 622 dumpRecords(pw, prefix, filter, mRecords); 623 pw.println("Restored without uid:"); 624 dumpRecords(pw, prefix, filter, mRestoredWithoutUids); 625 } 626 627 private static void dumpRecords(PrintWriter pw, String prefix, 628 NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) { 629 final int N = records.size(); 630 for (int i = 0; i < N; i++) { 631 final Record r = records.valueAt(i); 632 if (filter == null || filter.matches(r.pkg)) { 633 pw.print(prefix); 634 pw.print(" "); 635 pw.print(r.pkg); 636 pw.print(" ("); 637 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); 638 pw.print(')'); 639 if (r.importance != DEFAULT_IMPORTANCE) { 640 pw.print(" importance="); 641 pw.print(Ranking.importanceToString(r.importance)); 642 } 643 if (r.priority != DEFAULT_PRIORITY) { 644 pw.print(" priority="); 645 pw.print(Notification.priorityToString(r.priority)); 646 } 647 if (r.visibility != DEFAULT_VISIBILITY) { 648 pw.print(" visibility="); 649 pw.print(Notification.visibilityToString(r.visibility)); 650 } 651 pw.println(); 652 for (NotificationChannel channel : r.channels.values()) { 653 pw.print(prefix); 654 pw.print(" "); 655 pw.print(" "); 656 pw.println(channel); 657 } 658 } 659 } 660 } 661 662 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) { 663 JSONObject ranking = new JSONObject(); 664 JSONArray records = new JSONArray(); 665 try { 666 ranking.put("noUid", mRestoredWithoutUids.size()); 667 } catch (JSONException e) { 668 // pass 669 } 670 final int N = mRecords.size(); 671 for (int i = 0; i < N; i++) { 672 final Record r = mRecords.valueAt(i); 673 if (filter == null || filter.matches(r.pkg)) { 674 JSONObject record = new JSONObject(); 675 try { 676 record.put("userId", UserHandle.getUserId(r.uid)); 677 record.put("packageName", r.pkg); 678 if (r.importance != DEFAULT_IMPORTANCE) { 679 record.put("importance", Ranking.importanceToString(r.importance)); 680 } 681 if (r.priority != DEFAULT_PRIORITY) { 682 record.put("priority", Notification.priorityToString(r.priority)); 683 } 684 if (r.visibility != DEFAULT_VISIBILITY) { 685 record.put("visibility", Notification.visibilityToString(r.visibility)); 686 } 687 for (NotificationChannel channel : r.channels.values()) { 688 record.put("channel", channel.toJson()); 689 } 690 } catch (JSONException e) { 691 // pass 692 } 693 records.put(record); 694 } 695 } 696 try { 697 ranking.put("records", records); 698 } catch (JSONException e) { 699 // pass 700 } 701 return ranking; 702 } 703 704 /** 705 * Dump only the ban information as structured JSON for the stats collector. 706 * 707 * This is intentionally redundant with {#link dumpJson} because the old 708 * scraper will expect this format. 709 * 710 * @param filter 711 * @return 712 */ 713 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) { 714 JSONArray bans = new JSONArray(); 715 Map<Integer, String> packageBans = getPackageBans(); 716 for(Entry<Integer, String> ban : packageBans.entrySet()) { 717 final int userId = UserHandle.getUserId(ban.getKey()); 718 final String packageName = ban.getValue(); 719 if (filter == null || filter.matches(packageName)) { 720 JSONObject banJson = new JSONObject(); 721 try { 722 banJson.put("userId", userId); 723 banJson.put("packageName", packageName); 724 } catch (JSONException e) { 725 e.printStackTrace(); 726 } 727 bans.put(banJson); 728 } 729 } 730 return bans; 731 } 732 733 public Map<Integer, String> getPackageBans() { 734 final int N = mRecords.size(); 735 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N); 736 for (int i = 0; i < N; i++) { 737 final Record r = mRecords.valueAt(i); 738 if (r.importance == NotificationManager.IMPORTANCE_NONE) { 739 packageBans.put(r.uid, r.pkg); 740 } 741 } 742 return packageBans; 743 } 744 745 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList) { 746 if (removingPackage || pkgList == null || pkgList.length == 0) { 747 return; // nothing to do 748 } 749 boolean updated = false; 750 for (String pkg : pkgList) { 751 final Record r = mRestoredWithoutUids.get(pkg); 752 if (r != null) { 753 try { 754 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId); 755 mRestoredWithoutUids.remove(pkg); 756 mRecords.put(recordKey(r.pkg, r.uid), r); 757 updated = true; 758 } catch (NameNotFoundException e) { 759 // noop 760 } 761 } 762 try { 763 Record fullRecord = getRecord(pkg, 764 mPm.getPackageUidAsUser(pkg, changeUserId)); 765 if (fullRecord != null) { 766 clampDefaultChannel(fullRecord); 767 } 768 } catch (NameNotFoundException e) {} 769 } 770 771 if (updated) { 772 updateConfig(); 773 } 774 } 775 776 private static class Record { 777 static int UNKNOWN_UID = UserHandle.USER_NULL; 778 779 String pkg; 780 int uid = UNKNOWN_UID; 781 int importance = DEFAULT_IMPORTANCE; 782 int priority = DEFAULT_PRIORITY; 783 int visibility = DEFAULT_VISIBILITY; 784 785 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); 786 } 787} 788