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