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