RankingHelper.java revision 9f7fd6c734527b33e40a9f0d8199c961d52694bb
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 clearLockedFields(channel); 450 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 451 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 452 } 453 r.channels.put(channel.getId(), channel); 454 updateConfig(); 455 } 456 457 private void clearLockedFields(NotificationChannel channel) { 458 int clearMask = 0; 459 for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) { 460 clearMask |= NotificationChannel.LOCKABLE_FIELDS[i]; 461 } 462 channel.lockFields(~clearMask); 463 } 464 465 @Override 466 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) { 467 Preconditions.checkNotNull(updatedChannel); 468 Preconditions.checkNotNull(updatedChannel.getId()); 469 Record r = getOrCreateRecord(pkg, uid); 470 if (r == null) { 471 throw new IllegalArgumentException("Invalid package"); 472 } 473 NotificationChannel channel = r.channels.get(updatedChannel.getId()); 474 if (channel == null) { 475 throw new IllegalArgumentException("Channel does not exist"); 476 } 477 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 478 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 479 } 480 r.channels.put(updatedChannel.getId(), updatedChannel); 481 updateConfig(); 482 } 483 484 @Override 485 public void updateNotificationChannelFromAssistant(String pkg, int uid, 486 NotificationChannel updatedChannel) { 487 Record r = getOrCreateRecord(pkg, uid); 488 if (r == null) { 489 throw new IllegalArgumentException("Invalid package"); 490 } 491 NotificationChannel channel = r.channels.get(updatedChannel.getId()); 492 if (channel == null) { 493 throw new IllegalArgumentException("Channel does not exist"); 494 } 495 496 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) { 497 channel.setImportance(updatedChannel.getImportance()); 498 } 499 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { 500 channel.setLights(updatedChannel.shouldShowLights()); 501 } 502 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0) { 503 channel.setBypassDnd(updatedChannel.canBypassDnd()); 504 } 505 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) { 506 channel.setSound(updatedChannel.getSound()); 507 } 508 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { 509 channel.enableVibration(updatedChannel.shouldVibrate()); 510 channel.setVibrationPattern(updatedChannel.getVibrationPattern()); 511 } 512 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0) { 513 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 514 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 515 } else { 516 channel.setLockscreenVisibility(updatedChannel.getLockscreenVisibility()); 517 } 518 } 519 if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0) { 520 channel.setShowBadge(updatedChannel.canShowBadge()); 521 } 522 523 r.channels.put(channel.getId(), channel); 524 updateConfig(); 525 } 526 527 @Override 528 public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid, 529 String channelId) { 530 Record r = getOrCreateRecord(pkg, uid); 531 if (channelId == null) { 532 channelId = NotificationChannel.DEFAULT_CHANNEL_ID; 533 } 534 NotificationChannel channel = r.channels.get(channelId); 535 if (channel != null) { 536 return channel; 537 } else { 538 return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID); 539 } 540 } 541 542 @Override 543 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) { 544 Preconditions.checkNotNull(pkg); 545 Record r = getOrCreateRecord(pkg, uid); 546 if (r == null) { 547 throw new IllegalArgumentException("Invalid package"); 548 } 549 if (channelId == null) { 550 channelId = NotificationChannel.DEFAULT_CHANNEL_ID; 551 } 552 return r.channels.get(channelId); 553 } 554 555 @Override 556 public void deleteNotificationChannel(String pkg, int uid, String channelId) { 557 Preconditions.checkNotNull(pkg); 558 Preconditions.checkNotNull(channelId); 559 Record r = getRecord(pkg, uid); 560 if (r == null) { 561 throw new IllegalArgumentException("Invalid package"); 562 } 563 if (r != null) { 564 r.channels.remove(channelId); 565 } 566 } 567 568 @Override 569 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid) { 570 Preconditions.checkNotNull(pkg); 571 List<NotificationChannel> channels = new ArrayList<>(); 572 Record r = getRecord(pkg, uid); 573 if (r == null) { 574 throw new IllegalArgumentException("Invalid package"); 575 } 576 int N = r.channels.size(); 577 for (int i = 0; i < N; i++) { 578 channels.add(r.channels.valueAt(i)); 579 } 580 return new ParceledListSlice<>(channels); 581 } 582 583 /** 584 * Sets importance. 585 */ 586 @Override 587 public void setImportance(String pkgName, int uid, int importance) { 588 getOrCreateRecord(pkgName, uid).importance = importance; 589 updateConfig(); 590 } 591 592 public void setEnabled(String packageName, int uid, boolean enabled) { 593 boolean wasEnabled = getImportance(packageName, uid) != NotificationManager.IMPORTANCE_NONE; 594 if (wasEnabled == enabled) { 595 return; 596 } 597 setImportance(packageName, uid, 598 enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE); 599 } 600 601 public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { 602 if (filter == null) { 603 final int N = mSignalExtractors.length; 604 pw.print(prefix); 605 pw.print("mSignalExtractors.length = "); 606 pw.println(N); 607 for (int i = 0; i < N; i++) { 608 pw.print(prefix); 609 pw.print(" "); 610 pw.println(mSignalExtractors[i]); 611 } 612 } 613 if (filter == null) { 614 pw.print(prefix); 615 pw.println("per-package config:"); 616 } 617 pw.println("Records:"); 618 dumpRecords(pw, prefix, filter, mRecords); 619 pw.println("Restored without uid:"); 620 dumpRecords(pw, prefix, filter, mRestoredWithoutUids); 621 } 622 623 private static void dumpRecords(PrintWriter pw, String prefix, 624 NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) { 625 final int N = records.size(); 626 for (int i = 0; i < N; i++) { 627 final Record r = records.valueAt(i); 628 if (filter == null || filter.matches(r.pkg)) { 629 pw.print(prefix); 630 pw.print(" "); 631 pw.print(r.pkg); 632 pw.print(" ("); 633 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); 634 pw.print(')'); 635 if (r.importance != DEFAULT_IMPORTANCE) { 636 pw.print(" importance="); 637 pw.print(Ranking.importanceToString(r.importance)); 638 } 639 if (r.priority != DEFAULT_PRIORITY) { 640 pw.print(" priority="); 641 pw.print(Notification.priorityToString(r.priority)); 642 } 643 if (r.visibility != DEFAULT_VISIBILITY) { 644 pw.print(" visibility="); 645 pw.print(Notification.visibilityToString(r.visibility)); 646 } 647 pw.println(); 648 for (NotificationChannel channel : r.channels.values()) { 649 pw.print(prefix); 650 pw.print(" "); 651 pw.print(" "); 652 pw.println(channel); 653 } 654 } 655 } 656 } 657 658 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) { 659 JSONObject ranking = new JSONObject(); 660 JSONArray records = new JSONArray(); 661 try { 662 ranking.put("noUid", mRestoredWithoutUids.size()); 663 } catch (JSONException e) { 664 // pass 665 } 666 final int N = mRecords.size(); 667 for (int i = 0; i < N; i++) { 668 final Record r = mRecords.valueAt(i); 669 if (filter == null || filter.matches(r.pkg)) { 670 JSONObject record = new JSONObject(); 671 try { 672 record.put("userId", UserHandle.getUserId(r.uid)); 673 record.put("packageName", r.pkg); 674 if (r.importance != DEFAULT_IMPORTANCE) { 675 record.put("importance", Ranking.importanceToString(r.importance)); 676 } 677 if (r.priority != DEFAULT_PRIORITY) { 678 record.put("priority", Notification.priorityToString(r.priority)); 679 } 680 if (r.visibility != DEFAULT_VISIBILITY) { 681 record.put("visibility", Notification.visibilityToString(r.visibility)); 682 } 683 for (NotificationChannel channel : r.channels.values()) { 684 record.put("channel", channel.toJson()); 685 } 686 } catch (JSONException e) { 687 // pass 688 } 689 records.put(record); 690 } 691 } 692 try { 693 ranking.put("records", records); 694 } catch (JSONException e) { 695 // pass 696 } 697 return ranking; 698 } 699 700 /** 701 * Dump only the ban information as structured JSON for the stats collector. 702 * 703 * This is intentionally redundant with {#link dumpJson} because the old 704 * scraper will expect this format. 705 * 706 * @param filter 707 * @return 708 */ 709 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) { 710 JSONArray bans = new JSONArray(); 711 Map<Integer, String> packageBans = getPackageBans(); 712 for(Entry<Integer, String> ban : packageBans.entrySet()) { 713 final int userId = UserHandle.getUserId(ban.getKey()); 714 final String packageName = ban.getValue(); 715 if (filter == null || filter.matches(packageName)) { 716 JSONObject banJson = new JSONObject(); 717 try { 718 banJson.put("userId", userId); 719 banJson.put("packageName", packageName); 720 } catch (JSONException e) { 721 e.printStackTrace(); 722 } 723 bans.put(banJson); 724 } 725 } 726 return bans; 727 } 728 729 public Map<Integer, String> getPackageBans() { 730 final int N = mRecords.size(); 731 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N); 732 for (int i = 0; i < N; i++) { 733 final Record r = mRecords.valueAt(i); 734 if (r.importance == NotificationManager.IMPORTANCE_NONE) { 735 packageBans.put(r.uid, r.pkg); 736 } 737 } 738 return packageBans; 739 } 740 741 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList) { 742 if (removingPackage || pkgList == null || pkgList.length == 0) { 743 return; // nothing to do 744 } 745 boolean updated = false; 746 for (String pkg : pkgList) { 747 final Record r = mRestoredWithoutUids.get(pkg); 748 if (r != null) { 749 try { 750 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId); 751 mRestoredWithoutUids.remove(pkg); 752 mRecords.put(recordKey(r.pkg, r.uid), r); 753 updated = true; 754 } catch (NameNotFoundException e) { 755 // noop 756 } 757 } 758 try { 759 Record fullRecord = getRecord(pkg, 760 mPm.getPackageUidAsUser(pkg, changeUserId)); 761 if (fullRecord != null) { 762 clampDefaultChannel(fullRecord); 763 } 764 } catch (NameNotFoundException e) {} 765 } 766 767 if (updated) { 768 updateConfig(); 769 } 770 } 771 772 private static class Record { 773 static int UNKNOWN_UID = UserHandle.USER_NULL; 774 775 String pkg; 776 int uid = UNKNOWN_UID; 777 int importance = DEFAULT_IMPORTANCE; 778 int priority = DEFAULT_PRIORITY; 779 int visibility = DEFAULT_VISIBILITY; 780 781 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); 782 } 783} 784