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