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