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