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 */ 16 package com.android.server.notification; 17 18 import com.android.internal.R; 19 import com.android.internal.annotations.VisibleForTesting; 20 import com.android.internal.logging.MetricsLogger; 21 import com.android.internal.logging.nano.MetricsProto; 22 import com.android.internal.util.Preconditions; 23 24 import android.app.Notification; 25 import android.app.NotificationChannel; 26 import android.app.NotificationChannelGroup; 27 import android.app.NotificationManager; 28 import android.content.Context; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.content.pm.ParceledListSlice; 33 import android.metrics.LogMaker; 34 import android.os.Build; 35 import android.os.UserHandle; 36 import android.provider.Settings.Secure; 37 import android.service.notification.NotificationListenerService.Ranking; 38 import android.text.TextUtils; 39 import android.util.ArrayMap; 40 import android.util.Slog; 41 import android.util.SparseBooleanArray; 42 43 import org.json.JSONArray; 44 import org.json.JSONException; 45 import org.json.JSONObject; 46 import org.xmlpull.v1.XmlPullParser; 47 import org.xmlpull.v1.XmlPullParserException; 48 import org.xmlpull.v1.XmlSerializer; 49 50 import java.io.IOException; 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Collection; 55 import java.util.Collections; 56 import java.util.concurrent.ConcurrentHashMap; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.Map.Entry; 60 import java.util.Objects; 61 62 public class RankingHelper implements RankingConfig { 63 private static final String TAG = "RankingHelper"; 64 65 private static final int XML_VERSION = 1; 66 67 private static final String TAG_RANKING = "ranking"; 68 private static final String TAG_PACKAGE = "package"; 69 private static final String TAG_CHANNEL = "channel"; 70 private static final String TAG_GROUP = "channelGroup"; 71 72 private static final String ATT_VERSION = "version"; 73 private static final String ATT_NAME = "name"; 74 private static final String ATT_UID = "uid"; 75 private static final String ATT_ID = "id"; 76 private static final String ATT_PRIORITY = "priority"; 77 private static final String ATT_VISIBILITY = "visibility"; 78 private static final String ATT_IMPORTANCE = "importance"; 79 private static final String ATT_SHOW_BADGE = "show_badge"; 80 81 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; 82 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; 83 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; 84 private static final boolean DEFAULT_SHOW_BADGE = true; 85 86 private final NotificationSignalExtractor[] mSignalExtractors; 87 private final NotificationComparator mPreliminaryComparator; 88 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator(); 89 90 private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record 91 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>(); 92 private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record 93 94 private final Context mContext; 95 private final RankingHandler mRankingHandler; 96 private final PackageManager mPm; 97 private SparseBooleanArray mBadgingEnabled; 98 RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler, NotificationUsageStats usageStats, String[] extractorNames)99 public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler, 100 NotificationUsageStats usageStats, String[] extractorNames) { 101 mContext = context; 102 mRankingHandler = rankingHandler; 103 mPm = pm; 104 105 mPreliminaryComparator = new NotificationComparator(mContext); 106 107 updateBadgingEnabled(); 108 109 final int N = extractorNames.length; 110 mSignalExtractors = new NotificationSignalExtractor[N]; 111 for (int i = 0; i < N; i++) { 112 try { 113 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]); 114 NotificationSignalExtractor extractor = 115 (NotificationSignalExtractor) extractorClass.newInstance(); 116 extractor.initialize(mContext, usageStats); 117 extractor.setConfig(this); 118 mSignalExtractors[i] = extractor; 119 } catch (ClassNotFoundException e) { 120 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e); 121 } catch (InstantiationException e) { 122 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e); 123 } catch (IllegalAccessException e) { 124 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e); 125 } 126 } 127 } 128 129 @SuppressWarnings("unchecked") findExtractor(Class<T> extractorClass)130 public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) { 131 final int N = mSignalExtractors.length; 132 for (int i = 0; i < N; i++) { 133 final NotificationSignalExtractor extractor = mSignalExtractors[i]; 134 if (extractorClass.equals(extractor.getClass())) { 135 return (T) extractor; 136 } 137 } 138 return null; 139 } 140 extractSignals(NotificationRecord r)141 public void extractSignals(NotificationRecord r) { 142 final int N = mSignalExtractors.length; 143 for (int i = 0; i < N; i++) { 144 NotificationSignalExtractor extractor = mSignalExtractors[i]; 145 try { 146 RankingReconsideration recon = extractor.process(r); 147 if (recon != null) { 148 mRankingHandler.requestReconsideration(recon); 149 } 150 } catch (Throwable t) { 151 Slog.w(TAG, "NotificationSignalExtractor failed.", t); 152 } 153 } 154 } 155 readXml(XmlPullParser parser, boolean forRestore)156 public void readXml(XmlPullParser parser, boolean forRestore) 157 throws XmlPullParserException, IOException { 158 int type = parser.getEventType(); 159 if (type != XmlPullParser.START_TAG) return; 160 String tag = parser.getName(); 161 if (!TAG_RANKING.equals(tag)) return; 162 // Clobber groups and channels with the xml, but don't delete other data that wasn't present 163 // at the time of serialization. 164 mRestoredWithoutUids.clear(); 165 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 166 tag = parser.getName(); 167 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { 168 return; 169 } 170 if (type == XmlPullParser.START_TAG) { 171 if (TAG_PACKAGE.equals(tag)) { 172 int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID); 173 String name = parser.getAttributeValue(null, ATT_NAME); 174 if (!TextUtils.isEmpty(name)) { 175 if (forRestore) { 176 try { 177 //TODO: http://b/22388012 178 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM); 179 } catch (NameNotFoundException e) { 180 // noop 181 } 182 } 183 184 Record r = getOrCreateRecord(name, uid, 185 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE), 186 safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY), 187 safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY), 188 safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE)); 189 r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); 190 r.priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY); 191 r.visibility = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY); 192 r.showBadge = safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE); 193 194 final int innerDepth = parser.getDepth(); 195 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 196 && (type != XmlPullParser.END_TAG 197 || parser.getDepth() > innerDepth)) { 198 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 199 continue; 200 } 201 202 String tagName = parser.getName(); 203 // Channel groups 204 if (TAG_GROUP.equals(tagName)) { 205 String id = parser.getAttributeValue(null, ATT_ID); 206 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); 207 if (!TextUtils.isEmpty(id)) { 208 NotificationChannelGroup group 209 = new NotificationChannelGroup(id, groupName); 210 r.groups.put(id, group); 211 } 212 } 213 // Channels 214 if (TAG_CHANNEL.equals(tagName)) { 215 String id = parser.getAttributeValue(null, ATT_ID); 216 String channelName = parser.getAttributeValue(null, ATT_NAME); 217 int channelImportance = 218 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); 219 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) { 220 NotificationChannel channel = new NotificationChannel(id, 221 channelName, channelImportance); 222 channel.populateFromXml(parser); 223 r.channels.put(id, channel); 224 } 225 } 226 } 227 228 try { 229 deleteDefaultChannelIfNeeded(r); 230 } catch (NameNotFoundException e) { 231 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e); 232 } 233 } 234 } 235 } 236 } 237 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 238 } 239 recordKey(String pkg, int uid)240 private static String recordKey(String pkg, int uid) { 241 return pkg + "|" + uid; 242 } 243 getRecord(String pkg, int uid)244 private Record getRecord(String pkg, int uid) { 245 final String key = recordKey(pkg, uid); 246 synchronized (mRecords) { 247 return mRecords.get(key); 248 } 249 } 250 getOrCreateRecord(String pkg, int uid)251 private Record getOrCreateRecord(String pkg, int uid) { 252 return getOrCreateRecord(pkg, uid, 253 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE); 254 } 255 getOrCreateRecord(String pkg, int uid, int importance, int priority, int visibility, boolean showBadge)256 private Record getOrCreateRecord(String pkg, int uid, int importance, int priority, 257 int visibility, boolean showBadge) { 258 final String key = recordKey(pkg, uid); 259 synchronized (mRecords) { 260 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get( 261 key); 262 if (r == null) { 263 r = new Record(); 264 r.pkg = pkg; 265 r.uid = uid; 266 r.importance = importance; 267 r.priority = priority; 268 r.visibility = visibility; 269 r.showBadge = showBadge; 270 271 try { 272 createDefaultChannelIfNeeded(r); 273 } catch (NameNotFoundException e) { 274 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e); 275 } 276 277 if (r.uid == Record.UNKNOWN_UID) { 278 mRestoredWithoutUids.put(pkg, r); 279 } else { 280 mRecords.put(key, r); 281 } 282 } 283 return r; 284 } 285 } 286 shouldHaveDefaultChannel(Record r)287 private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException { 288 final int userId = UserHandle.getUserId(r.uid); 289 final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId); 290 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) { 291 // O apps should not have the default channel. 292 return false; 293 } 294 295 // Otherwise, this app should have the default channel. 296 return true; 297 } 298 deleteDefaultChannelIfNeeded(Record r)299 private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException { 300 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 301 // Not present 302 return; 303 } 304 305 if (shouldHaveDefaultChannel(r)) { 306 // Keep the default channel until upgraded. 307 return; 308 } 309 310 // Remove Default Channel. 311 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID); 312 } 313 createDefaultChannelIfNeeded(Record r)314 private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException { 315 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 316 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName( 317 mContext.getString(R.string.default_notification_channel_label)); 318 return; 319 } 320 321 if (!shouldHaveDefaultChannel(r)) { 322 // Keep the default channel until upgraded. 323 return; 324 } 325 326 // Create Default Channel 327 NotificationChannel channel; 328 channel = new NotificationChannel( 329 NotificationChannel.DEFAULT_CHANNEL_ID, 330 mContext.getString(R.string.default_notification_channel_label), 331 r.importance); 332 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); 333 channel.setLockscreenVisibility(r.visibility); 334 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) { 335 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 336 } 337 if (r.priority != DEFAULT_PRIORITY) { 338 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); 339 } 340 if (r.visibility != DEFAULT_VISIBILITY) { 341 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); 342 } 343 r.channels.put(channel.getId(), channel); 344 } 345 writeXml(XmlSerializer out, boolean forBackup)346 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException { 347 out.startTag(null, TAG_RANKING); 348 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); 349 350 synchronized (mRecords) { 351 final int N = mRecords.size(); 352 for (int i = 0; i < N; i++) { 353 final Record r = mRecords.valueAt(i); 354 //TODO: http://b/22388012 355 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) { 356 continue; 357 } 358 final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE 359 || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY 360 || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0 361 || r.groups.size() > 0; 362 if (hasNonDefaultSettings) { 363 out.startTag(null, TAG_PACKAGE); 364 out.attribute(null, ATT_NAME, r.pkg); 365 if (r.importance != DEFAULT_IMPORTANCE) { 366 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance)); 367 } 368 if (r.priority != DEFAULT_PRIORITY) { 369 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority)); 370 } 371 if (r.visibility != DEFAULT_VISIBILITY) { 372 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); 373 } 374 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge)); 375 376 if (!forBackup) { 377 out.attribute(null, ATT_UID, Integer.toString(r.uid)); 378 } 379 380 for (NotificationChannelGroup group : r.groups.values()) { 381 group.writeXml(out); 382 } 383 384 for (NotificationChannel channel : r.channels.values()) { 385 if (!forBackup || (forBackup && !channel.isDeleted())) { 386 channel.writeXml(out); 387 } 388 } 389 390 out.endTag(null, TAG_PACKAGE); 391 } 392 } 393 } 394 out.endTag(null, TAG_RANKING); 395 } 396 updateConfig()397 private void updateConfig() { 398 final int N = mSignalExtractors.length; 399 for (int i = 0; i < N; i++) { 400 mSignalExtractors[i].setConfig(this); 401 } 402 mRankingHandler.requestSort(false); 403 } 404 sort(ArrayList<NotificationRecord> notificationList)405 public void sort(ArrayList<NotificationRecord> notificationList) { 406 final int N = notificationList.size(); 407 // clear global sort keys 408 for (int i = N - 1; i >= 0; i--) { 409 notificationList.get(i).setGlobalSortKey(null); 410 } 411 412 // rank each record individually 413 Collections.sort(notificationList, mPreliminaryComparator); 414 415 synchronized (mProxyByGroupTmp) { 416 // record individual ranking result and nominate proxies for each group 417 for (int i = N - 1; i >= 0; i--) { 418 final NotificationRecord record = notificationList.get(i); 419 record.setAuthoritativeRank(i); 420 final String groupKey = record.getGroupKey(); 421 NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey); 422 if (existingProxy == null 423 || record.getImportance() > existingProxy.getImportance()) { 424 mProxyByGroupTmp.put(groupKey, record); 425 } 426 } 427 // assign global sort key: 428 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank 429 for (int i = 0; i < N; i++) { 430 final NotificationRecord record = notificationList.get(i); 431 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey()); 432 String groupSortKey = record.getNotification().getSortKey(); 433 434 // We need to make sure the developer provided group sort key (gsk) is handled 435 // correctly: 436 // gsk="" < gsk=non-null-string < gsk=null 437 // 438 // We enforce this by using different prefixes for these three cases. 439 String groupSortKeyPortion; 440 if (groupSortKey == null) { 441 groupSortKeyPortion = "nsk"; 442 } else if (groupSortKey.equals("")) { 443 groupSortKeyPortion = "esk"; 444 } else { 445 groupSortKeyPortion = "gsk=" + groupSortKey; 446 } 447 448 boolean isGroupSummary = record.getNotification().isGroupSummary(); 449 record.setGlobalSortKey( 450 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", 451 record.isRecentlyIntrusive() 452 && record.getImportance() > NotificationManager.IMPORTANCE_MIN 453 ? '0' : '1', 454 groupProxy.getAuthoritativeRank(), 455 isGroupSummary ? '0' : '1', 456 groupSortKeyPortion, 457 record.getAuthoritativeRank())); 458 } 459 mProxyByGroupTmp.clear(); 460 } 461 462 // Do a second ranking pass, using group proxies 463 Collections.sort(notificationList, mFinalComparator); 464 } 465 indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target)466 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) { 467 return Collections.binarySearch(notificationList, target, mFinalComparator); 468 } 469 safeBool(XmlPullParser parser, String att, boolean defValue)470 private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) { 471 final String value = parser.getAttributeValue(null, att); 472 if (TextUtils.isEmpty(value)) return defValue; 473 return Boolean.parseBoolean(value); 474 } 475 safeInt(XmlPullParser parser, String att, int defValue)476 private static int safeInt(XmlPullParser parser, String att, int defValue) { 477 final String val = parser.getAttributeValue(null, att); 478 return tryParseInt(val, defValue); 479 } 480 tryParseInt(String value, int defValue)481 private static int tryParseInt(String value, int defValue) { 482 if (TextUtils.isEmpty(value)) return defValue; 483 try { 484 return Integer.parseInt(value); 485 } catch (NumberFormatException e) { 486 return defValue; 487 } 488 } 489 490 /** 491 * Gets importance. 492 */ 493 @Override getImportance(String packageName, int uid)494 public int getImportance(String packageName, int uid) { 495 return getOrCreateRecord(packageName, uid).importance; 496 } 497 498 @Override canShowBadge(String packageName, int uid)499 public boolean canShowBadge(String packageName, int uid) { 500 return getOrCreateRecord(packageName, uid).showBadge; 501 } 502 503 @Override setShowBadge(String packageName, int uid, boolean showBadge)504 public void setShowBadge(String packageName, int uid, boolean showBadge) { 505 getOrCreateRecord(packageName, uid).showBadge = showBadge; 506 updateConfig(); 507 } 508 getPackagePriority(String pkg, int uid)509 int getPackagePriority(String pkg, int uid) { 510 return getOrCreateRecord(pkg, uid).priority; 511 } 512 getPackageVisibility(String pkg, int uid)513 int getPackageVisibility(String pkg, int uid) { 514 return getOrCreateRecord(pkg, uid).visibility; 515 } 516 517 @Override createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp)518 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, 519 boolean fromTargetApp) { 520 Preconditions.checkNotNull(pkg); 521 Preconditions.checkNotNull(group); 522 Preconditions.checkNotNull(group.getId()); 523 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName())); 524 Record r = getOrCreateRecord(pkg, uid); 525 if (r == null) { 526 throw new IllegalArgumentException("Invalid package"); 527 } 528 final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); 529 if (!group.equals(oldGroup)) { 530 // will log for new entries as well as name changes 531 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg)); 532 } 533 r.groups.put(group.getId(), group); 534 updateConfig(); 535 } 536 537 @Override createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp)538 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel, 539 boolean fromTargetApp) { 540 Preconditions.checkNotNull(pkg); 541 Preconditions.checkNotNull(channel); 542 Preconditions.checkNotNull(channel.getId()); 543 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName())); 544 Record r = getOrCreateRecord(pkg, uid); 545 if (r == null) { 546 throw new IllegalArgumentException("Invalid package"); 547 } 548 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) { 549 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist"); 550 } 551 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { 552 throw new IllegalArgumentException("Reserved id"); 553 } 554 555 NotificationChannel existing = r.channels.get(channel.getId()); 556 // Keep most of the existing settings 557 if (existing != null && fromTargetApp) { 558 if (existing.isDeleted()) { 559 existing.setDeleted(false); 560 561 // log a resurrected channel as if it's new again 562 MetricsLogger.action(getChannelLog(channel, pkg).setType( 563 MetricsProto.MetricsEvent.TYPE_OPEN)); 564 } 565 566 existing.setName(channel.getName().toString()); 567 existing.setDescription(channel.getDescription()); 568 existing.setBlockableSystem(channel.isBlockableSystem()); 569 570 updateConfig(); 571 return; 572 } 573 if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE 574 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) { 575 throw new IllegalArgumentException("Invalid importance level"); 576 } 577 // Reset fields that apps aren't allowed to set. 578 if (fromTargetApp) { 579 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); 580 channel.setLockscreenVisibility(r.visibility); 581 } 582 clearLockedFields(channel); 583 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 584 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 585 } 586 if (!r.showBadge) { 587 channel.setShowBadge(false); 588 } 589 r.channels.put(channel.getId(), channel); 590 MetricsLogger.action(getChannelLog(channel, pkg).setType( 591 MetricsProto.MetricsEvent.TYPE_OPEN)); 592 updateConfig(); 593 } 594 clearLockedFields(NotificationChannel channel)595 void clearLockedFields(NotificationChannel channel) { 596 channel.unlockFields(channel.getUserLockedFields()); 597 } 598 599 @Override updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel)600 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) { 601 Preconditions.checkNotNull(updatedChannel); 602 Preconditions.checkNotNull(updatedChannel.getId()); 603 Record r = getOrCreateRecord(pkg, uid); 604 if (r == null) { 605 throw new IllegalArgumentException("Invalid package"); 606 } 607 NotificationChannel channel = r.channels.get(updatedChannel.getId()); 608 if (channel == null || channel.isDeleted()) { 609 throw new IllegalArgumentException("Channel does not exist"); 610 } 611 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 612 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); 613 } 614 lockFieldsForUpdate(channel, updatedChannel); 615 r.channels.put(updatedChannel.getId(), updatedChannel); 616 617 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) { 618 // copy settings to app level so they are inherited by new channels 619 // when the app migrates 620 r.importance = updatedChannel.getImportance(); 621 r.priority = updatedChannel.canBypassDnd() 622 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT; 623 r.visibility = updatedChannel.getLockscreenVisibility(); 624 r.showBadge = updatedChannel.canShowBadge(); 625 } 626 627 if (!channel.equals(updatedChannel)) { 628 // only log if there are real changes 629 MetricsLogger.action(getChannelLog(updatedChannel, pkg)); 630 } 631 updateConfig(); 632 } 633 634 @Override getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted)635 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, 636 boolean includeDeleted) { 637 Preconditions.checkNotNull(pkg); 638 Record r = getOrCreateRecord(pkg, uid); 639 if (r == null) { 640 return null; 641 } 642 if (channelId == null) { 643 channelId = NotificationChannel.DEFAULT_CHANNEL_ID; 644 } 645 final NotificationChannel nc = r.channels.get(channelId); 646 if (nc != null && (includeDeleted || !nc.isDeleted())) { 647 return nc; 648 } 649 return null; 650 } 651 652 @Override deleteNotificationChannel(String pkg, int uid, String channelId)653 public void deleteNotificationChannel(String pkg, int uid, String channelId) { 654 Record r = getRecord(pkg, uid); 655 if (r == null) { 656 return; 657 } 658 NotificationChannel channel = r.channels.get(channelId); 659 if (channel != null) { 660 channel.setDeleted(true); 661 LogMaker lm = getChannelLog(channel, pkg); 662 lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); 663 MetricsLogger.action(lm); 664 updateConfig(); 665 } 666 } 667 668 @Override 669 @VisibleForTesting permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId)670 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) { 671 Preconditions.checkNotNull(pkg); 672 Preconditions.checkNotNull(channelId); 673 Record r = getRecord(pkg, uid); 674 if (r == null) { 675 return; 676 } 677 r.channels.remove(channelId); 678 updateConfig(); 679 } 680 681 @Override permanentlyDeleteNotificationChannels(String pkg, int uid)682 public void permanentlyDeleteNotificationChannels(String pkg, int uid) { 683 Preconditions.checkNotNull(pkg); 684 Record r = getRecord(pkg, uid); 685 if (r == null) { 686 return; 687 } 688 int N = r.channels.size() - 1; 689 for (int i = N; i >= 0; i--) { 690 String key = r.channels.keyAt(i); 691 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) { 692 r.channels.remove(key); 693 } 694 } 695 updateConfig(); 696 } 697 getNotificationChannelGroup(String groupId, String pkg, int uid)698 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg, 699 int uid) { 700 Preconditions.checkNotNull(pkg); 701 Record r = getRecord(pkg, uid); 702 return r.groups.get(groupId); 703 } 704 705 @Override getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted)706 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, 707 int uid, boolean includeDeleted) { 708 Preconditions.checkNotNull(pkg); 709 Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); 710 Record r = getRecord(pkg, uid); 711 if (r == null) { 712 return ParceledListSlice.emptyList(); 713 } 714 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); 715 int N = r.channels.size(); 716 for (int i = 0; i < N; i++) { 717 final NotificationChannel nc = r.channels.valueAt(i); 718 if (includeDeleted || !nc.isDeleted()) { 719 if (nc.getGroup() != null) { 720 if (r.groups.get(nc.getGroup()) != null) { 721 NotificationChannelGroup ncg = groups.get(nc.getGroup()); 722 if (ncg == null) { 723 ncg = r.groups.get(nc.getGroup()).clone(); 724 groups.put(nc.getGroup(), ncg); 725 726 } 727 ncg.addChannel(nc); 728 } 729 } else { 730 nonGrouped.addChannel(nc); 731 } 732 } 733 } 734 if (nonGrouped.getChannels().size() > 0) { 735 groups.put(null, nonGrouped); 736 } 737 return new ParceledListSlice<>(new ArrayList<>(groups.values())); 738 } 739 deleteNotificationChannelGroup(String pkg, int uid, String groupId)740 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid, 741 String groupId) { 742 List<NotificationChannel> deletedChannels = new ArrayList<>(); 743 Record r = getRecord(pkg, uid); 744 if (r == null || TextUtils.isEmpty(groupId)) { 745 return deletedChannels; 746 } 747 748 r.groups.remove(groupId); 749 750 int N = r.channels.size(); 751 for (int i = 0; i < N; i++) { 752 final NotificationChannel nc = r.channels.valueAt(i); 753 if (groupId.equals(nc.getGroup())) { 754 nc.setDeleted(true); 755 deletedChannels.add(nc); 756 } 757 } 758 updateConfig(); 759 return deletedChannels; 760 } 761 762 @Override getNotificationChannelGroups(String pkg, int uid)763 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg, 764 int uid) { 765 Record r = getRecord(pkg, uid); 766 if (r == null) { 767 return new ArrayList<>(); 768 } 769 return r.groups.values(); 770 } 771 772 @Override getNotificationChannels(String pkg, int uid, boolean includeDeleted)773 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, 774 boolean includeDeleted) { 775 Preconditions.checkNotNull(pkg); 776 List<NotificationChannel> channels = new ArrayList<>(); 777 Record r = getRecord(pkg, uid); 778 if (r == null) { 779 return ParceledListSlice.emptyList(); 780 } 781 int N = r.channels.size(); 782 for (int i = 0; i < N; i++) { 783 final NotificationChannel nc = r.channels.valueAt(i); 784 if (includeDeleted || !nc.isDeleted()) { 785 channels.add(nc); 786 } 787 } 788 return new ParceledListSlice<>(channels); 789 } 790 791 /** 792 * True for pre-O apps that only have the default channel, or pre O apps that have no 793 * channels yet. This method will create the default channel for pre-O apps that don't have it. 794 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app 795 * upgrades. 796 */ onlyHasDefaultChannel(String pkg, int uid)797 public boolean onlyHasDefaultChannel(String pkg, int uid) { 798 Record r = getOrCreateRecord(pkg, uid); 799 if (r.channels.size() == 1 800 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 801 return true; 802 } 803 return false; 804 } 805 getDeletedChannelCount(String pkg, int uid)806 public int getDeletedChannelCount(String pkg, int uid) { 807 Preconditions.checkNotNull(pkg); 808 int deletedCount = 0; 809 Record r = getRecord(pkg, uid); 810 if (r == null) { 811 return deletedCount; 812 } 813 int N = r.channels.size(); 814 for (int i = 0; i < N; i++) { 815 final NotificationChannel nc = r.channels.valueAt(i); 816 if (nc.isDeleted()) { 817 deletedCount++; 818 } 819 } 820 return deletedCount; 821 } 822 823 /** 824 * Sets importance. 825 */ 826 @Override setImportance(String pkgName, int uid, int importance)827 public void setImportance(String pkgName, int uid, int importance) { 828 getOrCreateRecord(pkgName, uid).importance = importance; 829 updateConfig(); 830 } 831 setEnabled(String packageName, int uid, boolean enabled)832 public void setEnabled(String packageName, int uid, boolean enabled) { 833 boolean wasEnabled = getImportance(packageName, uid) != NotificationManager.IMPORTANCE_NONE; 834 if (wasEnabled == enabled) { 835 return; 836 } 837 setImportance(packageName, uid, 838 enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE); 839 } 840 841 @VisibleForTesting lockFieldsForUpdate(NotificationChannel original, NotificationChannel update)842 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) { 843 update.unlockFields(update.getUserLockedFields()); 844 update.lockFields(original.getUserLockedFields()); 845 if (original.canBypassDnd() != update.canBypassDnd()) { 846 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); 847 } 848 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) { 849 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); 850 } 851 if (original.getImportance() != update.getImportance()) { 852 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 853 } 854 if (original.shouldShowLights() != update.shouldShowLights() 855 || original.getLightColor() != update.getLightColor()) { 856 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS); 857 } 858 if (!Objects.equals(original.getSound(), update.getSound())) { 859 update.lockFields(NotificationChannel.USER_LOCKED_SOUND); 860 } 861 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern()) 862 || original.shouldVibrate() != update.shouldVibrate()) { 863 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION); 864 } 865 if (original.canShowBadge() != update.canShowBadge()) { 866 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE); 867 } 868 } 869 dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter)870 public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { 871 if (filter == null) { 872 final int N = mSignalExtractors.length; 873 pw.print(prefix); 874 pw.print("mSignalExtractors.length = "); 875 pw.println(N); 876 for (int i = 0; i < N; i++) { 877 pw.print(prefix); 878 pw.print(" "); 879 pw.println(mSignalExtractors[i]); 880 } 881 } 882 if (filter == null) { 883 pw.print(prefix); 884 pw.println("per-package config:"); 885 } 886 pw.println("Records:"); 887 synchronized (mRecords) { 888 dumpRecords(pw, prefix, filter, mRecords); 889 } 890 pw.println("Restored without uid:"); 891 dumpRecords(pw, prefix, filter, mRestoredWithoutUids); 892 } 893 dumpRecords(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records)894 private static void dumpRecords(PrintWriter pw, String prefix, 895 NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) { 896 final int N = records.size(); 897 for (int i = 0; i < N; i++) { 898 final Record r = records.valueAt(i); 899 if (filter == null || filter.matches(r.pkg)) { 900 pw.print(prefix); 901 pw.print(" AppSettings: "); 902 pw.print(r.pkg); 903 pw.print(" ("); 904 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); 905 pw.print(')'); 906 if (r.importance != DEFAULT_IMPORTANCE) { 907 pw.print(" importance="); 908 pw.print(Ranking.importanceToString(r.importance)); 909 } 910 if (r.priority != DEFAULT_PRIORITY) { 911 pw.print(" priority="); 912 pw.print(Notification.priorityToString(r.priority)); 913 } 914 if (r.visibility != DEFAULT_VISIBILITY) { 915 pw.print(" visibility="); 916 pw.print(Notification.visibilityToString(r.visibility)); 917 } 918 pw.print(" showBadge="); 919 pw.print(Boolean.toString(r.showBadge)); 920 pw.println(); 921 for (NotificationChannel channel : r.channels.values()) { 922 pw.print(prefix); 923 pw.print(" "); 924 pw.print(" "); 925 pw.println(channel); 926 } 927 for (NotificationChannelGroup group : r.groups.values()) { 928 pw.print(prefix); 929 pw.print(" "); 930 pw.print(" "); 931 pw.println(group); 932 } 933 } 934 } 935 } 936 dumpJson(NotificationManagerService.DumpFilter filter)937 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) { 938 JSONObject ranking = new JSONObject(); 939 JSONArray records = new JSONArray(); 940 try { 941 ranking.put("noUid", mRestoredWithoutUids.size()); 942 } catch (JSONException e) { 943 // pass 944 } 945 synchronized (mRecords) { 946 final int N = mRecords.size(); 947 for (int i = 0; i < N; i++) { 948 final Record r = mRecords.valueAt(i); 949 if (filter == null || filter.matches(r.pkg)) { 950 JSONObject record = new JSONObject(); 951 try { 952 record.put("userId", UserHandle.getUserId(r.uid)); 953 record.put("packageName", r.pkg); 954 if (r.importance != DEFAULT_IMPORTANCE) { 955 record.put("importance", Ranking.importanceToString(r.importance)); 956 } 957 if (r.priority != DEFAULT_PRIORITY) { 958 record.put("priority", Notification.priorityToString(r.priority)); 959 } 960 if (r.visibility != DEFAULT_VISIBILITY) { 961 record.put("visibility", Notification.visibilityToString(r.visibility)); 962 } 963 if (r.showBadge != DEFAULT_SHOW_BADGE) { 964 record.put("showBadge", Boolean.valueOf(r.showBadge)); 965 } 966 for (NotificationChannel channel : r.channels.values()) { 967 record.put("channel", channel.toJson()); 968 } 969 for (NotificationChannelGroup group : r.groups.values()) { 970 record.put("group", group.toJson()); 971 } 972 } catch (JSONException e) { 973 // pass 974 } 975 records.put(record); 976 } 977 } 978 } 979 try { 980 ranking.put("records", records); 981 } catch (JSONException e) { 982 // pass 983 } 984 return ranking; 985 } 986 987 /** 988 * Dump only the ban information as structured JSON for the stats collector. 989 * 990 * This is intentionally redundant with {#link dumpJson} because the old 991 * scraper will expect this format. 992 * 993 * @param filter 994 * @return 995 */ dumpBansJson(NotificationManagerService.DumpFilter filter)996 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) { 997 JSONArray bans = new JSONArray(); 998 Map<Integer, String> packageBans = getPackageBans(); 999 for(Entry<Integer, String> ban : packageBans.entrySet()) { 1000 final int userId = UserHandle.getUserId(ban.getKey()); 1001 final String packageName = ban.getValue(); 1002 if (filter == null || filter.matches(packageName)) { 1003 JSONObject banJson = new JSONObject(); 1004 try { 1005 banJson.put("userId", userId); 1006 banJson.put("packageName", packageName); 1007 } catch (JSONException e) { 1008 e.printStackTrace(); 1009 } 1010 bans.put(banJson); 1011 } 1012 } 1013 return bans; 1014 } 1015 getPackageBans()1016 public Map<Integer, String> getPackageBans() { 1017 synchronized (mRecords) { 1018 final int N = mRecords.size(); 1019 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N); 1020 for (int i = 0; i < N; i++) { 1021 final Record r = mRecords.valueAt(i); 1022 if (r.importance == NotificationManager.IMPORTANCE_NONE) { 1023 packageBans.put(r.uid, r.pkg); 1024 } 1025 } 1026 1027 return packageBans; 1028 } 1029 } 1030 1031 /** 1032 * Dump only the channel information as structured JSON for the stats collector. 1033 * 1034 * This is intentionally redundant with {#link dumpJson} because the old 1035 * scraper will expect this format. 1036 * 1037 * @param filter 1038 * @return 1039 */ dumpChannelsJson(NotificationManagerService.DumpFilter filter)1040 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) { 1041 JSONArray channels = new JSONArray(); 1042 Map<String, Integer> packageChannels = getPackageChannels(); 1043 for(Entry<String, Integer> channelCount : packageChannels.entrySet()) { 1044 final String packageName = channelCount.getKey(); 1045 if (filter == null || filter.matches(packageName)) { 1046 JSONObject channelCountJson = new JSONObject(); 1047 try { 1048 channelCountJson.put("packageName", packageName); 1049 channelCountJson.put("channelCount", channelCount.getValue()); 1050 } catch (JSONException e) { 1051 e.printStackTrace(); 1052 } 1053 channels.put(channelCountJson); 1054 } 1055 } 1056 return channels; 1057 } 1058 getPackageChannels()1059 private Map<String, Integer> getPackageChannels() { 1060 ArrayMap<String, Integer> packageChannels = new ArrayMap<>(); 1061 synchronized (mRecords) { 1062 for (int i = 0; i < mRecords.size(); i++) { 1063 final Record r = mRecords.valueAt(i); 1064 int channelCount = 0; 1065 for (int j = 0; j < r.channels.size(); j++) { 1066 if (!r.channels.valueAt(j).isDeleted()) { 1067 channelCount++; 1068 } 1069 } 1070 packageChannels.put(r.pkg, channelCount); 1071 } 1072 } 1073 return packageChannels; 1074 } 1075 onUserRemoved(int userId)1076 public void onUserRemoved(int userId) { 1077 synchronized (mRecords) { 1078 int N = mRecords.size(); 1079 for (int i = N - 1; i >= 0 ; i--) { 1080 Record record = mRecords.valueAt(i); 1081 if (UserHandle.getUserId(record.uid) == userId) { 1082 mRecords.removeAt(i); 1083 } 1084 } 1085 } 1086 } 1087 onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList)1088 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, 1089 int[] uidList) { 1090 if (pkgList == null || pkgList.length == 0) { 1091 return; // nothing to do 1092 } 1093 boolean updated = false; 1094 if (removingPackage) { 1095 // Remove notification settings for uninstalled package 1096 int size = Math.min(pkgList.length, uidList.length); 1097 for (int i = 0; i < size; i++) { 1098 final String pkg = pkgList[i]; 1099 final int uid = uidList[i]; 1100 synchronized (mRecords) { 1101 mRecords.remove(recordKey(pkg, uid)); 1102 } 1103 mRestoredWithoutUids.remove(pkg); 1104 updated = true; 1105 } 1106 } else { 1107 for (String pkg : pkgList) { 1108 // Package install 1109 final Record r = mRestoredWithoutUids.get(pkg); 1110 if (r != null) { 1111 try { 1112 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId); 1113 mRestoredWithoutUids.remove(pkg); 1114 synchronized (mRecords) { 1115 mRecords.put(recordKey(r.pkg, r.uid), r); 1116 } 1117 updated = true; 1118 } catch (NameNotFoundException e) { 1119 // noop 1120 } 1121 } 1122 // Package upgrade 1123 try { 1124 Record fullRecord = getRecord(pkg, 1125 mPm.getPackageUidAsUser(pkg, changeUserId)); 1126 if (fullRecord != null) { 1127 createDefaultChannelIfNeeded(fullRecord); 1128 deleteDefaultChannelIfNeeded(fullRecord); 1129 } 1130 } catch (NameNotFoundException e) {} 1131 } 1132 } 1133 1134 if (updated) { 1135 updateConfig(); 1136 } 1137 } 1138 getChannelLog(NotificationChannel channel, String pkg)1139 private LogMaker getChannelLog(NotificationChannel channel, String pkg) { 1140 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL) 1141 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) 1142 .setPackageName(pkg) 1143 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, 1144 channel.getId()) 1145 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, 1146 channel.getImportance()); 1147 } 1148 getChannelGroupLog(String groupId, String pkg)1149 private LogMaker getChannelGroupLog(String groupId, String pkg) { 1150 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP) 1151 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) 1152 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID, 1153 groupId) 1154 .setPackageName(pkg); 1155 } 1156 updateBadgingEnabled()1157 public void updateBadgingEnabled() { 1158 if (mBadgingEnabled == null) { 1159 mBadgingEnabled = new SparseBooleanArray(); 1160 } 1161 boolean changed = false; 1162 // update the cached values 1163 for (int index = 0; index < mBadgingEnabled.size(); index++) { 1164 int userId = mBadgingEnabled.keyAt(index); 1165 final boolean oldValue = mBadgingEnabled.get(userId); 1166 final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(), 1167 Secure.NOTIFICATION_BADGING, 1168 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0; 1169 mBadgingEnabled.put(userId, newValue); 1170 changed |= oldValue != newValue; 1171 } 1172 if (changed) { 1173 mRankingHandler.requestSort(false); 1174 } 1175 } 1176 badgingEnabled(UserHandle userHandle)1177 public boolean badgingEnabled(UserHandle userHandle) { 1178 int userId = userHandle.getIdentifier(); 1179 if (userId == UserHandle.USER_ALL) { 1180 return false; 1181 } 1182 if (mBadgingEnabled.indexOfKey(userId) < 0) { 1183 mBadgingEnabled.put(userId, 1184 Secure.getIntForUser(mContext.getContentResolver(), 1185 Secure.NOTIFICATION_BADGING, 1186 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0); 1187 } 1188 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE); 1189 } 1190 1191 1192 private static class Record { 1193 static int UNKNOWN_UID = UserHandle.USER_NULL; 1194 1195 String pkg; 1196 int uid = UNKNOWN_UID; 1197 int importance = DEFAULT_IMPORTANCE; 1198 int priority = DEFAULT_PRIORITY; 1199 int visibility = DEFAULT_VISIBILITY; 1200 boolean showBadge = DEFAULT_SHOW_BADGE; 1201 1202 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); 1203 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>(); 1204 } 1205 } 1206