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