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