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