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