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