1 /** 2 * Copyright (c) 2018, 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 17 package com.android.server.notification; 18 19 import static android.app.NotificationManager.IMPORTANCE_NONE; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.Notification; 25 import android.app.NotificationChannel; 26 import android.app.NotificationChannelGroup; 27 import android.app.NotificationManager; 28 import android.content.Context; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ParceledListSlice; 32 import android.metrics.LogMaker; 33 import android.os.Build; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.service.notification.NotificationListenerService; 37 import android.service.notification.RankingHelperProto; 38 import android.text.TextUtils; 39 import android.util.ArrayMap; 40 import android.util.ArraySet; 41 import android.util.Pair; 42 import android.util.Slog; 43 import android.util.SparseBooleanArray; 44 import android.util.proto.ProtoOutputStream; 45 46 import com.android.internal.R; 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.logging.MetricsLogger; 49 import com.android.internal.util.Preconditions; 50 import com.android.internal.util.XmlUtils; 51 52 import org.json.JSONArray; 53 import org.json.JSONException; 54 import org.json.JSONObject; 55 import org.xmlpull.v1.XmlPullParser; 56 import org.xmlpull.v1.XmlPullParserException; 57 import org.xmlpull.v1.XmlSerializer; 58 59 import java.io.IOException; 60 import java.io.PrintWriter; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Collection; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Objects; 67 import java.util.concurrent.ConcurrentHashMap; 68 69 public class PreferencesHelper implements RankingConfig { 70 private static final String TAG = "NotificationPrefHelper"; 71 private static final int XML_VERSION = 1; 72 private static final int UNKNOWN_UID = UserHandle.USER_NULL; 73 private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; 74 75 @VisibleForTesting 76 static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000; 77 78 @VisibleForTesting 79 static final String TAG_RANKING = "ranking"; 80 private static final String TAG_PACKAGE = "package"; 81 private static final String TAG_CHANNEL = "channel"; 82 private static final String TAG_GROUP = "channelGroup"; 83 private static final String TAG_DELEGATE = "delegate"; 84 private static final String TAG_STATUS_ICONS = "silent_status_icons"; 85 86 private static final String ATT_VERSION = "version"; 87 private static final String ATT_NAME = "name"; 88 private static final String ATT_UID = "uid"; 89 private static final String ATT_ID = "id"; 90 private static final String ATT_ALLOW_BUBBLE = "allow_bubble"; 91 private static final String ATT_PRIORITY = "priority"; 92 private static final String ATT_VISIBILITY = "visibility"; 93 private static final String ATT_IMPORTANCE = "importance"; 94 private static final String ATT_SHOW_BADGE = "show_badge"; 95 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields"; 96 private static final String ATT_ENABLED = "enabled"; 97 private static final String ATT_USER_ALLOWED = "allowed"; 98 private static final String ATT_HIDE_SILENT = "hide_gentle"; 99 100 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; 101 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; 102 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; 103 @VisibleForTesting 104 static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false; 105 private static final boolean DEFAULT_SHOW_BADGE = true; 106 static final boolean DEFAULT_ALLOW_BUBBLE = true; 107 private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false; 108 private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false; 109 110 /** 111 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable 112 * fields. 113 */ 114 private static final int DEFAULT_LOCKED_APP_FIELDS = 0; 115 116 /** 117 * All user-lockable fields for a given application. 118 */ 119 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE}) 120 public @interface LockableAppFields { 121 int USER_LOCKED_IMPORTANCE = 0x00000001; 122 int USER_LOCKED_BUBBLE = 0x00000002; 123 } 124 125 // pkg|uid => PackagePreferences 126 private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>(); 127 // pkg => PackagePreferences 128 private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>(); 129 130 private final Context mContext; 131 private final PackageManager mPm; 132 private final RankingHandler mRankingHandler; 133 private final ZenModeHelper mZenModeHelper; 134 135 private SparseBooleanArray mBadgingEnabled; 136 private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE; 137 private boolean mAreChannelsBypassingDnd; 138 private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; 139 PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper)140 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, 141 ZenModeHelper zenHelper) { 142 mContext = context; 143 mZenModeHelper = zenHelper; 144 mRankingHandler = rankingHandler; 145 mPm = pm; 146 147 updateBadgingEnabled(); 148 updateBubblesEnabled(); 149 syncChannelsBypassingDnd(mContext.getUserId()); 150 } 151 readXml(XmlPullParser parser, boolean forRestore, int userId)152 public void readXml(XmlPullParser parser, boolean forRestore, int userId) 153 throws XmlPullParserException, IOException { 154 int type = parser.getEventType(); 155 if (type != XmlPullParser.START_TAG) return; 156 String tag = parser.getName(); 157 if (!TAG_RANKING.equals(tag)) return; 158 synchronized (mPackagePreferences) { 159 // Clobber groups and channels with the xml, but don't delete other data that wasn't 160 // present at the time of serialization. 161 mRestoredWithoutUids.clear(); 162 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 163 tag = parser.getName(); 164 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { 165 return; 166 } 167 if (type == XmlPullParser.START_TAG) { 168 if (TAG_STATUS_ICONS.equals(tag)) { 169 if (forRestore && userId != UserHandle.USER_SYSTEM) { 170 continue; 171 } 172 mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute( 173 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS); 174 } else if (TAG_PACKAGE.equals(tag)) { 175 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID); 176 String name = parser.getAttributeValue(null, ATT_NAME); 177 if (!TextUtils.isEmpty(name)) { 178 if (forRestore) { 179 try { 180 uid = mPm.getPackageUidAsUser(name, userId); 181 } catch (PackageManager.NameNotFoundException e) { 182 // noop 183 } 184 } 185 boolean skipWarningLogged = false; 186 187 PackagePreferences r = getOrCreatePackagePreferencesLocked(name, uid, 188 XmlUtils.readIntAttribute( 189 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE), 190 XmlUtils.readIntAttribute(parser, ATT_PRIORITY, 191 DEFAULT_PRIORITY), 192 XmlUtils.readIntAttribute( 193 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY), 194 XmlUtils.readBooleanAttribute( 195 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE), 196 XmlUtils.readBooleanAttribute( 197 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); 198 r.importance = XmlUtils.readIntAttribute( 199 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); 200 r.priority = XmlUtils.readIntAttribute( 201 parser, ATT_PRIORITY, DEFAULT_PRIORITY); 202 r.visibility = XmlUtils.readIntAttribute( 203 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY); 204 r.showBadge = XmlUtils.readBooleanAttribute( 205 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE); 206 r.lockedAppFields = XmlUtils.readIntAttribute(parser, 207 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS); 208 209 final int innerDepth = parser.getDepth(); 210 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 211 && (type != XmlPullParser.END_TAG 212 || parser.getDepth() > innerDepth)) { 213 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 214 continue; 215 } 216 217 String tagName = parser.getName(); 218 // Channel groups 219 if (TAG_GROUP.equals(tagName)) { 220 String id = parser.getAttributeValue(null, ATT_ID); 221 CharSequence groupName = parser.getAttributeValue(null, 222 ATT_NAME); 223 if (!TextUtils.isEmpty(id)) { 224 NotificationChannelGroup group 225 = new NotificationChannelGroup(id, groupName); 226 group.populateFromXml(parser); 227 r.groups.put(id, group); 228 } 229 } 230 // Channels 231 if (TAG_CHANNEL.equals(tagName)) { 232 if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) { 233 if (!skipWarningLogged) { 234 Slog.w(TAG, "Skipping further channels for " + r.pkg 235 + "; app has too many"); 236 skipWarningLogged = true; 237 } 238 continue; 239 } 240 String id = parser.getAttributeValue(null, ATT_ID); 241 String channelName = parser.getAttributeValue(null, ATT_NAME); 242 int channelImportance = XmlUtils.readIntAttribute( 243 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); 244 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) { 245 NotificationChannel channel = new NotificationChannel(id, 246 channelName, channelImportance); 247 if (forRestore) { 248 channel.populateFromXmlForRestore(parser, mContext); 249 } else { 250 channel.populateFromXml(parser); 251 } 252 channel.setImportanceLockedByCriticalDeviceFunction( 253 r.defaultAppLockedImportance); 254 r.channels.put(id, channel); 255 } 256 } 257 // Delegate 258 if (TAG_DELEGATE.equals(tagName)) { 259 int delegateId = 260 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID); 261 String delegateName = 262 XmlUtils.readStringAttribute(parser, ATT_NAME); 263 boolean delegateEnabled = XmlUtils.readBooleanAttribute( 264 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED); 265 boolean userAllowed = XmlUtils.readBooleanAttribute( 266 parser, ATT_USER_ALLOWED, 267 Delegate.DEFAULT_USER_ALLOWED); 268 Delegate d = null; 269 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty( 270 delegateName)) { 271 d = new Delegate( 272 delegateName, delegateId, delegateEnabled, 273 userAllowed); 274 } 275 r.delegate = d; 276 } 277 278 } 279 280 try { 281 deleteDefaultChannelIfNeededLocked(r); 282 } catch (PackageManager.NameNotFoundException e) { 283 Slog.e(TAG, "deleteDefaultChannelIfNeededLocked - Exception: " + e); 284 } 285 } 286 } 287 } 288 } 289 } 290 throw new IllegalStateException("Failed to reach END_DOCUMENT"); 291 } 292 getPackagePreferencesLocked(String pkg, int uid)293 private PackagePreferences getPackagePreferencesLocked(String pkg, int uid) { 294 final String key = packagePreferencesKey(pkg, uid); 295 return mPackagePreferences.get(key); 296 } 297 getOrCreatePackagePreferencesLocked(String pkg, int uid)298 private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid) { 299 return getOrCreatePackagePreferencesLocked(pkg, uid, 300 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE, 301 DEFAULT_ALLOW_BUBBLE); 302 } 303 getOrCreatePackagePreferencesLocked(String pkg, int uid, int importance, int priority, int visibility, boolean showBadge, boolean allowBubble)304 private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid, 305 int importance, int priority, int visibility, boolean showBadge, boolean allowBubble) { 306 final String key = packagePreferencesKey(pkg, uid); 307 PackagePreferences 308 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) 309 : mPackagePreferences.get(key); 310 if (r == null) { 311 r = new PackagePreferences(); 312 r.pkg = pkg; 313 r.uid = uid; 314 r.importance = importance; 315 r.priority = priority; 316 r.visibility = visibility; 317 r.showBadge = showBadge; 318 r.allowBubble = allowBubble; 319 320 try { 321 createDefaultChannelIfNeededLocked(r); 322 } catch (PackageManager.NameNotFoundException e) { 323 Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e); 324 } 325 326 if (r.uid == UNKNOWN_UID) { 327 mRestoredWithoutUids.put(pkg, r); 328 } else { 329 mPackagePreferences.put(key, r); 330 } 331 } 332 return r; 333 } 334 shouldHaveDefaultChannel(PackagePreferences r)335 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws 336 PackageManager.NameNotFoundException { 337 final int userId = UserHandle.getUserId(r.uid); 338 final ApplicationInfo applicationInfo = 339 mPm.getApplicationInfoAsUser(r.pkg, 0, userId); 340 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) { 341 // O apps should not have the default channel. 342 return false; 343 } 344 345 // Otherwise, this app should have the default channel. 346 return true; 347 } 348 deleteDefaultChannelIfNeededLocked(PackagePreferences r)349 private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws 350 PackageManager.NameNotFoundException { 351 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 352 // Not present 353 return false; 354 } 355 356 if (shouldHaveDefaultChannel(r)) { 357 // Keep the default channel until upgraded. 358 return false; 359 } 360 361 // Remove Default Channel. 362 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID); 363 364 return true; 365 } 366 createDefaultChannelIfNeededLocked(PackagePreferences r)367 private boolean createDefaultChannelIfNeededLocked(PackagePreferences r) throws 368 PackageManager.NameNotFoundException { 369 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 370 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString( 371 com.android.internal.R.string.default_notification_channel_label)); 372 return false; 373 } 374 375 if (!shouldHaveDefaultChannel(r)) { 376 // Keep the default channel until upgraded. 377 return false; 378 } 379 380 // Create Default Channel 381 NotificationChannel channel; 382 channel = new NotificationChannel( 383 NotificationChannel.DEFAULT_CHANNEL_ID, 384 mContext.getString(R.string.default_notification_channel_label), 385 r.importance); 386 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); 387 channel.setLockscreenVisibility(r.visibility); 388 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) { 389 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 390 } 391 if (r.priority != DEFAULT_PRIORITY) { 392 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); 393 } 394 if (r.visibility != DEFAULT_VISIBILITY) { 395 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); 396 } 397 r.channels.put(channel.getId(), channel); 398 399 return true; 400 } 401 writeXml(XmlSerializer out, boolean forBackup, int userId)402 public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException { 403 out.startTag(null, TAG_RANKING); 404 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION)); 405 if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS 406 && (!forBackup || userId == UserHandle.USER_SYSTEM)) { 407 out.startTag(null, TAG_STATUS_ICONS); 408 out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons)); 409 out.endTag(null, TAG_STATUS_ICONS); 410 } 411 412 synchronized (mPackagePreferences) { 413 final int N = mPackagePreferences.size(); 414 for (int i = 0; i < N; i++) { 415 final PackagePreferences r = mPackagePreferences.valueAt(i); 416 if (forBackup && UserHandle.getUserId(r.uid) != userId) { 417 continue; 418 } 419 final boolean hasNonDefaultSettings = 420 r.importance != DEFAULT_IMPORTANCE 421 || r.priority != DEFAULT_PRIORITY 422 || r.visibility != DEFAULT_VISIBILITY 423 || r.showBadge != DEFAULT_SHOW_BADGE 424 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS 425 || r.channels.size() > 0 426 || r.groups.size() > 0 427 || r.delegate != null 428 || r.allowBubble != DEFAULT_ALLOW_BUBBLE; 429 if (hasNonDefaultSettings) { 430 out.startTag(null, TAG_PACKAGE); 431 out.attribute(null, ATT_NAME, r.pkg); 432 if (r.importance != DEFAULT_IMPORTANCE) { 433 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance)); 434 } 435 if (r.priority != DEFAULT_PRIORITY) { 436 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority)); 437 } 438 if (r.visibility != DEFAULT_VISIBILITY) { 439 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility)); 440 } 441 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) { 442 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble)); 443 } 444 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge)); 445 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS, 446 Integer.toString(r.lockedAppFields)); 447 448 if (!forBackup) { 449 out.attribute(null, ATT_UID, Integer.toString(r.uid)); 450 } 451 452 if (r.delegate != null) { 453 out.startTag(null, TAG_DELEGATE); 454 455 out.attribute(null, ATT_NAME, r.delegate.mPkg); 456 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid)); 457 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) { 458 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled)); 459 } 460 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) { 461 out.attribute(null, ATT_USER_ALLOWED, 462 Boolean.toString(r.delegate.mUserAllowed)); 463 } 464 out.endTag(null, TAG_DELEGATE); 465 } 466 467 for (NotificationChannelGroup group : r.groups.values()) { 468 group.writeXml(out); 469 } 470 471 for (NotificationChannel channel : r.channels.values()) { 472 if (forBackup) { 473 if (!channel.isDeleted()) { 474 channel.writeXmlForBackup(out, mContext); 475 } 476 } else { 477 channel.writeXml(out); 478 } 479 } 480 481 out.endTag(null, TAG_PACKAGE); 482 } 483 } 484 } 485 out.endTag(null, TAG_RANKING); 486 } 487 488 /** 489 * Sets whether bubbles are allowed. 490 * 491 * @param pkg the package to allow or not allow bubbles for. 492 * @param uid the uid to allow or not allow bubbles for. 493 * @param allowed whether bubbles are allowed. 494 */ setBubblesAllowed(String pkg, int uid, boolean allowed)495 public void setBubblesAllowed(String pkg, int uid, boolean allowed) { 496 boolean changed = false; 497 synchronized (mPackagePreferences) { 498 PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid); 499 changed = p.allowBubble != allowed; 500 p.allowBubble = allowed; 501 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE; 502 } 503 if (changed) { 504 updateConfig(); 505 } 506 } 507 508 /** 509 * Whether bubbles are allowed. 510 * 511 * @param pkg the package to check if bubbles are allowed for 512 * @param uid the uid to check if bubbles are allowed for. 513 * @return whether bubbles are allowed. 514 */ 515 @Override areBubblesAllowed(String pkg, int uid)516 public boolean areBubblesAllowed(String pkg, int uid) { 517 synchronized (mPackagePreferences) { 518 return getOrCreatePackagePreferencesLocked(pkg, uid).allowBubble; 519 } 520 } 521 getAppLockedFields(String pkg, int uid)522 public int getAppLockedFields(String pkg, int uid) { 523 synchronized (mPackagePreferences) { 524 return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields; 525 } 526 } 527 528 /** 529 * Gets importance. 530 */ 531 @Override getImportance(String packageName, int uid)532 public int getImportance(String packageName, int uid) { 533 synchronized (mPackagePreferences) { 534 return getOrCreatePackagePreferencesLocked(packageName, uid).importance; 535 } 536 } 537 538 /** 539 * Returns whether the importance of the corresponding notification is user-locked and shouldn't 540 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel 541 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}. 542 */ getIsAppImportanceLocked(String packageName, int uid)543 public boolean getIsAppImportanceLocked(String packageName, int uid) { 544 synchronized (mPackagePreferences) { 545 int userLockedFields = getOrCreatePackagePreferencesLocked(packageName, uid).lockedAppFields; 546 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0; 547 } 548 } 549 550 @Override canShowBadge(String packageName, int uid)551 public boolean canShowBadge(String packageName, int uid) { 552 synchronized (mPackagePreferences) { 553 return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge; 554 } 555 } 556 557 @Override setShowBadge(String packageName, int uid, boolean showBadge)558 public void setShowBadge(String packageName, int uid, boolean showBadge) { 559 synchronized (mPackagePreferences) { 560 getOrCreatePackagePreferencesLocked(packageName, uid).showBadge = showBadge; 561 } 562 updateConfig(); 563 } 564 565 @Override isGroupBlocked(String packageName, int uid, String groupId)566 public boolean isGroupBlocked(String packageName, int uid, String groupId) { 567 if (groupId == null) { 568 return false; 569 } 570 synchronized (mPackagePreferences) { 571 PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); 572 NotificationChannelGroup group = r.groups.get(groupId); 573 if (group == null) { 574 return false; 575 } 576 return group.isBlocked(); 577 } 578 } 579 getPackagePriority(String pkg, int uid)580 int getPackagePriority(String pkg, int uid) { 581 synchronized (mPackagePreferences) { 582 return getOrCreatePackagePreferencesLocked(pkg, uid).priority; 583 } 584 } 585 getPackageVisibility(String pkg, int uid)586 int getPackageVisibility(String pkg, int uid) { 587 synchronized (mPackagePreferences) { 588 return getOrCreatePackagePreferencesLocked(pkg, uid).visibility; 589 } 590 } 591 592 @Override createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp)593 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, 594 boolean fromTargetApp) { 595 Preconditions.checkNotNull(pkg); 596 Preconditions.checkNotNull(group); 597 Preconditions.checkNotNull(group.getId()); 598 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName())); 599 synchronized (mPackagePreferences) { 600 PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); 601 if (r == null) { 602 throw new IllegalArgumentException("Invalid package"); 603 } 604 final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); 605 if (!group.equals(oldGroup)) { 606 // will log for new entries as well as name/description changes 607 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg)); 608 } 609 if (oldGroup != null) { 610 group.setChannels(oldGroup.getChannels()); 611 612 // apps can't update the blocked status or app overlay permission 613 if (fromTargetApp) { 614 group.setBlocked(oldGroup.isBlocked()); 615 group.unlockFields(group.getUserLockedFields()); 616 group.lockFields(oldGroup.getUserLockedFields()); 617 } else { 618 // but the system can 619 if (group.isBlocked() != oldGroup.isBlocked()) { 620 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE); 621 updateChannelsBypassingDnd(mContext.getUserId()); 622 } 623 } 624 } 625 r.groups.put(group.getId(), group); 626 } 627 } 628 629 @Override createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess)630 public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, 631 boolean fromTargetApp, boolean hasDndAccess) { 632 Preconditions.checkNotNull(pkg); 633 Preconditions.checkNotNull(channel); 634 Preconditions.checkNotNull(channel.getId()); 635 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName())); 636 boolean needsPolicyFileChange = false; 637 synchronized (mPackagePreferences) { 638 PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); 639 if (r == null) { 640 throw new IllegalArgumentException("Invalid package"); 641 } 642 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) { 643 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist"); 644 } 645 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { 646 throw new IllegalArgumentException("Reserved id"); 647 } 648 NotificationChannel existing = r.channels.get(channel.getId()); 649 // Keep most of the existing settings 650 if (existing != null && fromTargetApp) { 651 if (existing.isDeleted()) { 652 existing.setDeleted(false); 653 needsPolicyFileChange = true; 654 655 // log a resurrected channel as if it's new again 656 MetricsLogger.action(getChannelLog(channel, pkg).setType( 657 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); 658 } 659 660 if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) { 661 existing.setName(channel.getName().toString()); 662 needsPolicyFileChange = true; 663 } 664 if (!Objects.equals(channel.getDescription(), existing.getDescription())) { 665 existing.setDescription(channel.getDescription()); 666 needsPolicyFileChange = true; 667 } 668 if (channel.isBlockableSystem() != existing.isBlockableSystem()) { 669 existing.setBlockableSystem(channel.isBlockableSystem()); 670 needsPolicyFileChange = true; 671 } 672 if (channel.getGroup() != null && existing.getGroup() == null) { 673 existing.setGroup(channel.getGroup()); 674 needsPolicyFileChange = true; 675 } 676 677 // Apps are allowed to downgrade channel importance if the user has not changed any 678 // fields on this channel yet. 679 final int previousExistingImportance = existing.getImportance(); 680 if (existing.getUserLockedFields() == 0 && 681 channel.getImportance() < existing.getImportance()) { 682 existing.setImportance(channel.getImportance()); 683 needsPolicyFileChange = true; 684 } 685 686 // system apps and dnd access apps can bypass dnd if the user hasn't changed any 687 // fields on the channel yet 688 if (existing.getUserLockedFields() == 0 && hasDndAccess) { 689 boolean bypassDnd = channel.canBypassDnd(); 690 if (bypassDnd != existing.canBypassDnd()) { 691 existing.setBypassDnd(bypassDnd); 692 needsPolicyFileChange = true; 693 694 if (bypassDnd != mAreChannelsBypassingDnd 695 || previousExistingImportance != existing.getImportance()) { 696 updateChannelsBypassingDnd(mContext.getUserId()); 697 } 698 } 699 } 700 701 updateConfig(); 702 return needsPolicyFileChange; 703 } 704 705 if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) { 706 throw new IllegalStateException("Limit exceed; cannot create more channels"); 707 } 708 709 needsPolicyFileChange = true; 710 711 if (channel.getImportance() < IMPORTANCE_NONE 712 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) { 713 throw new IllegalArgumentException("Invalid importance level"); 714 } 715 716 // Reset fields that apps aren't allowed to set. 717 if (fromTargetApp && !hasDndAccess) { 718 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); 719 } 720 if (fromTargetApp) { 721 channel.setLockscreenVisibility(r.visibility); 722 } 723 clearLockedFieldsLocked(channel); 724 channel.setImportanceLockedByOEM(r.oemLockedImportance); 725 if (!channel.isImportanceLockedByOEM()) { 726 if (r.futureOemLockedChannels.remove(channel.getId())) { 727 channel.setImportanceLockedByOEM(true); 728 } 729 } 730 channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance); 731 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 732 channel.setLockscreenVisibility( 733 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); 734 } 735 if (!r.showBadge) { 736 channel.setShowBadge(false); 737 } 738 739 r.channels.put(channel.getId(), channel); 740 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) { 741 updateChannelsBypassingDnd(mContext.getUserId()); 742 } 743 MetricsLogger.action(getChannelLog(channel, pkg).setType( 744 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); 745 } 746 747 return needsPolicyFileChange; 748 } 749 clearLockedFieldsLocked(NotificationChannel channel)750 void clearLockedFieldsLocked(NotificationChannel channel) { 751 channel.unlockFields(channel.getUserLockedFields()); 752 } 753 754 @Override updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel, boolean fromUser)755 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel, 756 boolean fromUser) { 757 Preconditions.checkNotNull(updatedChannel); 758 Preconditions.checkNotNull(updatedChannel.getId()); 759 synchronized (mPackagePreferences) { 760 PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); 761 if (r == null) { 762 throw new IllegalArgumentException("Invalid package"); 763 } 764 NotificationChannel channel = r.channels.get(updatedChannel.getId()); 765 if (channel == null || channel.isDeleted()) { 766 throw new IllegalArgumentException("Channel does not exist"); 767 } 768 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { 769 updatedChannel.setLockscreenVisibility( 770 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE); 771 } 772 if (fromUser) { 773 updatedChannel.lockFields(channel.getUserLockedFields()); 774 lockFieldsForUpdateLocked(channel, updatedChannel); 775 } else { 776 updatedChannel.unlockFields(updatedChannel.getUserLockedFields()); 777 } 778 // no importance updates are allowed if OEM blocked it 779 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM()); 780 if (updatedChannel.isImportanceLockedByOEM()) { 781 updatedChannel.setImportance(channel.getImportance()); 782 } 783 updatedChannel.setImportanceLockedByCriticalDeviceFunction( 784 r.defaultAppLockedImportance); 785 if (updatedChannel.isImportanceLockedByCriticalDeviceFunction() 786 && updatedChannel.getImportance() == IMPORTANCE_NONE) { 787 updatedChannel.setImportance(channel.getImportance()); 788 } 789 790 r.channels.put(updatedChannel.getId(), updatedChannel); 791 792 if (onlyHasDefaultChannel(pkg, uid)) { 793 // copy settings to app level so they are inherited by new channels 794 // when the app migrates 795 r.importance = updatedChannel.getImportance(); 796 r.priority = updatedChannel.canBypassDnd() 797 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT; 798 r.visibility = updatedChannel.getLockscreenVisibility(); 799 r.showBadge = updatedChannel.canShowBadge(); 800 } 801 802 if (!channel.equals(updatedChannel)) { 803 // only log if there are real changes 804 MetricsLogger.action(getChannelLog(updatedChannel, pkg) 805 .setSubtype(fromUser ? 1 : 0)); 806 } 807 808 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd 809 || channel.getImportance() != updatedChannel.getImportance()) { 810 updateChannelsBypassingDnd(mContext.getUserId()); 811 } 812 } 813 updateConfig(); 814 } 815 816 @Override getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted)817 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, 818 boolean includeDeleted) { 819 Preconditions.checkNotNull(pkg); 820 synchronized (mPackagePreferences) { 821 PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); 822 if (r == null) { 823 return null; 824 } 825 if (channelId == null) { 826 channelId = NotificationChannel.DEFAULT_CHANNEL_ID; 827 } 828 final NotificationChannel nc = r.channels.get(channelId); 829 if (nc != null && (includeDeleted || !nc.isDeleted())) { 830 return nc; 831 } 832 return null; 833 } 834 } 835 836 @Override deleteNotificationChannel(String pkg, int uid, String channelId)837 public void deleteNotificationChannel(String pkg, int uid, String channelId) { 838 synchronized (mPackagePreferences) { 839 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 840 if (r == null) { 841 return; 842 } 843 NotificationChannel channel = r.channels.get(channelId); 844 if (channel != null) { 845 channel.setDeleted(true); 846 LogMaker lm = getChannelLog(channel, pkg); 847 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE); 848 MetricsLogger.action(lm); 849 850 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) { 851 updateChannelsBypassingDnd(mContext.getUserId()); 852 } 853 } 854 } 855 } 856 857 @Override 858 @VisibleForTesting permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId)859 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) { 860 Preconditions.checkNotNull(pkg); 861 Preconditions.checkNotNull(channelId); 862 synchronized (mPackagePreferences) { 863 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 864 if (r == null) { 865 return; 866 } 867 r.channels.remove(channelId); 868 } 869 } 870 871 @Override permanentlyDeleteNotificationChannels(String pkg, int uid)872 public void permanentlyDeleteNotificationChannels(String pkg, int uid) { 873 Preconditions.checkNotNull(pkg); 874 synchronized (mPackagePreferences) { 875 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 876 if (r == null) { 877 return; 878 } 879 int N = r.channels.size() - 1; 880 for (int i = N; i >= 0; i--) { 881 String key = r.channels.keyAt(i); 882 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) { 883 r.channels.remove(key); 884 } 885 } 886 } 887 } 888 shouldHideSilentStatusIcons()889 public boolean shouldHideSilentStatusIcons() { 890 return mHideSilentStatusBarIcons; 891 } 892 setHideSilentStatusIcons(boolean hide)893 public void setHideSilentStatusIcons(boolean hide) { 894 mHideSilentStatusBarIcons = hide; 895 } 896 lockChannelsForOEM(String[] appOrChannelList)897 public void lockChannelsForOEM(String[] appOrChannelList) { 898 if (appOrChannelList == null) { 899 return; 900 } 901 for (String appOrChannel : appOrChannelList) { 902 if (!TextUtils.isEmpty(appOrChannel)) { 903 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM); 904 if (appSplit != null && appSplit.length > 0) { 905 String appName = appSplit[0]; 906 String channelId = appSplit.length == 2 ? appSplit[1] : null; 907 908 synchronized (mPackagePreferences) { 909 for (PackagePreferences r : mPackagePreferences.values()) { 910 if (r.pkg.equals(appName)) { 911 if (channelId == null) { 912 // lock all channels for the app 913 r.oemLockedImportance = true; 914 for (NotificationChannel channel : r.channels.values()) { 915 channel.setImportanceLockedByOEM(true); 916 } 917 } else { 918 NotificationChannel channel = r.channels.get(channelId); 919 if (channel != null) { 920 channel.setImportanceLockedByOEM(true); 921 } else { 922 // if this channel shows up in the future, make sure it'll 923 // be locked immediately 924 r.futureOemLockedChannels.add(channelId); 925 } 926 } 927 } 928 } 929 } 930 } 931 } 932 } 933 } 934 updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd)935 public void updateDefaultApps(int userId, ArraySet<String> toRemove, 936 ArraySet<Pair<String, Integer>> toAdd) { 937 synchronized (mPackagePreferences) { 938 for (PackagePreferences p : mPackagePreferences.values()) { 939 if (userId == UserHandle.getUserId(p.uid)) { 940 if (toRemove != null && toRemove.contains(p.pkg)) { 941 p.defaultAppLockedImportance = false; 942 for (NotificationChannel channel : p.channels.values()) { 943 channel.setImportanceLockedByCriticalDeviceFunction(false); 944 } 945 } 946 } 947 } 948 if (toAdd != null) { 949 for (Pair<String, Integer> approvedApp : toAdd) { 950 PackagePreferences p = getOrCreatePackagePreferencesLocked(approvedApp.first, 951 approvedApp.second); 952 p.defaultAppLockedImportance = true; 953 for (NotificationChannel channel : p.channels.values()) { 954 channel.setImportanceLockedByCriticalDeviceFunction(true); 955 } 956 } 957 } 958 } 959 } 960 getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted)961 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg, 962 int uid, String groupId, boolean includeDeleted) { 963 Preconditions.checkNotNull(pkg); 964 synchronized (mPackagePreferences) { 965 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 966 if (r == null || groupId == null || !r.groups.containsKey(groupId)) { 967 return null; 968 } 969 NotificationChannelGroup group = r.groups.get(groupId).clone(); 970 group.setChannels(new ArrayList<>()); 971 int N = r.channels.size(); 972 for (int i = 0; i < N; i++) { 973 final NotificationChannel nc = r.channels.valueAt(i); 974 if (includeDeleted || !nc.isDeleted()) { 975 if (groupId.equals(nc.getGroup())) { 976 group.addChannel(nc); 977 } 978 } 979 } 980 return group; 981 } 982 } 983 getNotificationChannelGroup(String groupId, String pkg, int uid)984 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg, 985 int uid) { 986 Preconditions.checkNotNull(pkg); 987 synchronized (mPackagePreferences) { 988 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 989 if (r == null) { 990 return null; 991 } 992 return r.groups.get(groupId); 993 } 994 } 995 996 @Override getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty)997 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, 998 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) { 999 Preconditions.checkNotNull(pkg); 1000 Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); 1001 synchronized (mPackagePreferences) { 1002 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 1003 if (r == null) { 1004 return ParceledListSlice.emptyList(); 1005 } 1006 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null); 1007 int N = r.channels.size(); 1008 for (int i = 0; i < N; i++) { 1009 final NotificationChannel nc = r.channels.valueAt(i); 1010 if (includeDeleted || !nc.isDeleted()) { 1011 if (nc.getGroup() != null) { 1012 if (r.groups.get(nc.getGroup()) != null) { 1013 NotificationChannelGroup ncg = groups.get(nc.getGroup()); 1014 if (ncg == null) { 1015 ncg = r.groups.get(nc.getGroup()).clone(); 1016 ncg.setChannels(new ArrayList<>()); 1017 groups.put(nc.getGroup(), ncg); 1018 1019 } 1020 ncg.addChannel(nc); 1021 } 1022 } else { 1023 nonGrouped.addChannel(nc); 1024 } 1025 } 1026 } 1027 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) { 1028 groups.put(null, nonGrouped); 1029 } 1030 if (includeEmpty) { 1031 for (NotificationChannelGroup group : r.groups.values()) { 1032 if (!groups.containsKey(group.getId())) { 1033 groups.put(group.getId(), group); 1034 } 1035 } 1036 } 1037 return new ParceledListSlice<>(new ArrayList<>(groups.values())); 1038 } 1039 } 1040 deleteNotificationChannelGroup(String pkg, int uid, String groupId)1041 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid, 1042 String groupId) { 1043 List<NotificationChannel> deletedChannels = new ArrayList<>(); 1044 synchronized (mPackagePreferences) { 1045 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 1046 if (r == null || TextUtils.isEmpty(groupId)) { 1047 return deletedChannels; 1048 } 1049 1050 r.groups.remove(groupId); 1051 1052 int N = r.channels.size(); 1053 for (int i = 0; i < N; i++) { 1054 final NotificationChannel nc = r.channels.valueAt(i); 1055 if (groupId.equals(nc.getGroup())) { 1056 nc.setDeleted(true); 1057 deletedChannels.add(nc); 1058 } 1059 } 1060 } 1061 return deletedChannels; 1062 } 1063 1064 @Override getNotificationChannelGroups(String pkg, int uid)1065 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg, 1066 int uid) { 1067 List<NotificationChannelGroup> groups = new ArrayList<>(); 1068 synchronized (mPackagePreferences) { 1069 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 1070 if (r == null) { 1071 return groups; 1072 } 1073 groups.addAll(r.groups.values()); 1074 } 1075 return groups; 1076 } 1077 1078 @Override getNotificationChannels(String pkg, int uid, boolean includeDeleted)1079 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, 1080 boolean includeDeleted) { 1081 Preconditions.checkNotNull(pkg); 1082 List<NotificationChannel> channels = new ArrayList<>(); 1083 synchronized (mPackagePreferences) { 1084 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 1085 if (r == null) { 1086 return ParceledListSlice.emptyList(); 1087 } 1088 int N = r.channels.size(); 1089 for (int i = 0; i < N; i++) { 1090 final NotificationChannel nc = r.channels.valueAt(i); 1091 if (includeDeleted || !nc.isDeleted()) { 1092 channels.add(nc); 1093 } 1094 } 1095 return new ParceledListSlice<>(channels); 1096 } 1097 } 1098 1099 /** 1100 * Gets all notification channels associated with the given pkg and userId that can bypass dnd 1101 */ getNotificationChannelsBypassingDnd(String pkg, int userId)1102 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg, 1103 int userId) { 1104 List<NotificationChannel> channels = new ArrayList<>(); 1105 synchronized (mPackagePreferences) { 1106 final PackagePreferences r = mPackagePreferences.get( 1107 packagePreferencesKey(pkg, userId)); 1108 // notifications from this package aren't blocked 1109 if (r != null && r.importance != IMPORTANCE_NONE) { 1110 for (NotificationChannel channel : r.channels.values()) { 1111 if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) { 1112 channels.add(channel); 1113 } 1114 } 1115 } 1116 } 1117 return new ParceledListSlice<>(channels); 1118 } 1119 1120 /** 1121 * True for pre-O apps that only have the default channel, or pre O apps that have no 1122 * channels yet. This method will create the default channel for pre-O apps that don't have it. 1123 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app 1124 * upgrades. 1125 */ onlyHasDefaultChannel(String pkg, int uid)1126 public boolean onlyHasDefaultChannel(String pkg, int uid) { 1127 synchronized (mPackagePreferences) { 1128 PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); 1129 if (r.channels.size() == 1 1130 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { 1131 return true; 1132 } 1133 return false; 1134 } 1135 } 1136 getDeletedChannelCount(String pkg, int uid)1137 public int getDeletedChannelCount(String pkg, int uid) { 1138 Preconditions.checkNotNull(pkg); 1139 int deletedCount = 0; 1140 synchronized (mPackagePreferences) { 1141 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 1142 if (r == null) { 1143 return deletedCount; 1144 } 1145 int N = r.channels.size(); 1146 for (int i = 0; i < N; i++) { 1147 final NotificationChannel nc = r.channels.valueAt(i); 1148 if (nc.isDeleted()) { 1149 deletedCount++; 1150 } 1151 } 1152 return deletedCount; 1153 } 1154 } 1155 getBlockedChannelCount(String pkg, int uid)1156 public int getBlockedChannelCount(String pkg, int uid) { 1157 Preconditions.checkNotNull(pkg); 1158 int blockedCount = 0; 1159 synchronized (mPackagePreferences) { 1160 PackagePreferences r = getPackagePreferencesLocked(pkg, uid); 1161 if (r == null) { 1162 return blockedCount; 1163 } 1164 int N = r.channels.size(); 1165 for (int i = 0; i < N; i++) { 1166 final NotificationChannel nc = r.channels.valueAt(i); 1167 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) { 1168 blockedCount++; 1169 } 1170 } 1171 return blockedCount; 1172 } 1173 } 1174 getBlockedAppCount(int userId)1175 public int getBlockedAppCount(int userId) { 1176 int count = 0; 1177 synchronized (mPackagePreferences) { 1178 final int N = mPackagePreferences.size(); 1179 for (int i = 0; i < N; i++) { 1180 final PackagePreferences r = mPackagePreferences.valueAt(i); 1181 if (userId == UserHandle.getUserId(r.uid) 1182 && r.importance == IMPORTANCE_NONE) { 1183 count++; 1184 } 1185 } 1186 } 1187 return count; 1188 } 1189 1190 /** 1191 * Returns the number of apps that have at least one notification channel that can bypass DND 1192 * for given particular user 1193 */ getAppsBypassingDndCount(int userId)1194 public int getAppsBypassingDndCount(int userId) { 1195 int count = 0; 1196 synchronized (mPackagePreferences) { 1197 final int numPackagePreferences = mPackagePreferences.size(); 1198 for (int i = 0; i < numPackagePreferences; i++) { 1199 final PackagePreferences r = mPackagePreferences.valueAt(i); 1200 // Package isn't associated with this userId or notifications from this package are 1201 // blocked 1202 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) { 1203 continue; 1204 } 1205 1206 for (NotificationChannel channel : r.channels.values()) { 1207 if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) { 1208 count++; 1209 break; 1210 } 1211 } 1212 } 1213 } 1214 return count; 1215 } 1216 1217 /** 1218 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before 1219 * updating 1220 * @param userId 1221 */ syncChannelsBypassingDnd(int userId)1222 private void syncChannelsBypassingDnd(int userId) { 1223 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state 1224 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1; 1225 updateChannelsBypassingDnd(userId); 1226 } 1227 1228 /** 1229 * Updates the user's NotificationPolicy based on whether the given userId 1230 * has channels bypassing DND 1231 * @param userId 1232 */ updateChannelsBypassingDnd(int userId)1233 private void updateChannelsBypassingDnd(int userId) { 1234 synchronized (mPackagePreferences) { 1235 final int numPackagePreferences = mPackagePreferences.size(); 1236 for (int i = 0; i < numPackagePreferences; i++) { 1237 final PackagePreferences r = mPackagePreferences.valueAt(i); 1238 // Package isn't associated with this userId or notifications from this package are 1239 // blocked 1240 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) { 1241 continue; 1242 } 1243 1244 for (NotificationChannel channel : r.channels.values()) { 1245 if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) { 1246 if (!mAreChannelsBypassingDnd) { 1247 mAreChannelsBypassingDnd = true; 1248 updateZenPolicy(true); 1249 } 1250 return; 1251 } 1252 } 1253 } 1254 } 1255 // If no channels bypass DND, update the zen policy once to disable DND bypass. 1256 if (mAreChannelsBypassingDnd) { 1257 mAreChannelsBypassingDnd = false; 1258 updateZenPolicy(false); 1259 } 1260 } 1261 channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel)1262 private boolean channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel) { 1263 // Channel is in a group that's blocked 1264 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) { 1265 return false; 1266 } 1267 1268 // Channel is deleted or is blocked 1269 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) { 1270 return false; 1271 } 1272 1273 return true; 1274 } 1275 updateZenPolicy(boolean areChannelsBypassingDnd)1276 public void updateZenPolicy(boolean areChannelsBypassingDnd) { 1277 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy(); 1278 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy( 1279 policy.priorityCategories, policy.priorityCallSenders, 1280 policy.priorityMessageSenders, policy.suppressedVisualEffects, 1281 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND 1282 : 0))); 1283 } 1284 areChannelsBypassingDnd()1285 public boolean areChannelsBypassingDnd() { 1286 return mAreChannelsBypassingDnd; 1287 } 1288 1289 /** 1290 * Sets importance. 1291 */ 1292 @Override setImportance(String pkgName, int uid, int importance)1293 public void setImportance(String pkgName, int uid, int importance) { 1294 synchronized (mPackagePreferences) { 1295 getOrCreatePackagePreferencesLocked(pkgName, uid).importance = importance; 1296 } 1297 updateConfig(); 1298 } 1299 setEnabled(String packageName, int uid, boolean enabled)1300 public void setEnabled(String packageName, int uid, boolean enabled) { 1301 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE; 1302 if (wasEnabled == enabled) { 1303 return; 1304 } 1305 setImportance(packageName, uid, 1306 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE); 1307 } 1308 1309 /** 1310 * Sets whether any notifications from the app, represented by the given {@code pkgName} and 1311 * {@code uid}, have their importance locked by the user. Locked notifications don't get 1312 * considered for sentiment adjustments (and thus never show a blocking helper). 1313 */ setAppImportanceLocked(String packageName, int uid)1314 public void setAppImportanceLocked(String packageName, int uid) { 1315 synchronized (mPackagePreferences) { 1316 PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid); 1317 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) { 1318 return; 1319 } 1320 1321 prefs.lockedAppFields = 1322 prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE; 1323 } 1324 updateConfig(); 1325 } 1326 1327 /** 1328 * Returns the delegate for a given package, if it's allowed by the package and the user. 1329 */ getNotificationDelegate(String sourcePkg, int sourceUid)1330 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) { 1331 synchronized (mPackagePreferences) { 1332 PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); 1333 1334 if (prefs == null || prefs.delegate == null) { 1335 return null; 1336 } 1337 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) { 1338 return null; 1339 } 1340 return prefs.delegate.mPkg; 1341 } 1342 } 1343 1344 /** 1345 * Used by an app to delegate notification posting privileges to another apps. 1346 */ setNotificationDelegate(String sourcePkg, int sourceUid, String delegatePkg, int delegateUid)1347 public void setNotificationDelegate(String sourcePkg, int sourceUid, 1348 String delegatePkg, int delegateUid) { 1349 synchronized (mPackagePreferences) { 1350 PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid); 1351 1352 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed; 1353 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed); 1354 prefs.delegate = delegate; 1355 } 1356 updateConfig(); 1357 } 1358 1359 /** 1360 * Used by an app to turn off its notification delegate. 1361 */ revokeNotificationDelegate(String sourcePkg, int sourceUid)1362 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) { 1363 boolean changed = false; 1364 synchronized (mPackagePreferences) { 1365 PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); 1366 if (prefs != null && prefs.delegate != null) { 1367 prefs.delegate.mEnabled = false; 1368 changed = true; 1369 } 1370 } 1371 if (changed) { 1372 updateConfig(); 1373 } 1374 } 1375 1376 /** 1377 * Toggles whether an app can have a notification delegate on behalf of a user. 1378 */ toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed)1379 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) { 1380 boolean changed = false; 1381 synchronized (mPackagePreferences) { 1382 PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); 1383 if (prefs != null && prefs.delegate != null) { 1384 prefs.delegate.mUserAllowed = userAllowed; 1385 changed = true; 1386 } 1387 } 1388 if (changed) { 1389 updateConfig(); 1390 } 1391 } 1392 1393 /** 1394 * Returns whether the given app is allowed on post notifications on behalf of the other given 1395 * app. 1396 */ isDelegateAllowed(String sourcePkg, int sourceUid, String potentialDelegatePkg, int potentialDelegateUid)1397 public boolean isDelegateAllowed(String sourcePkg, int sourceUid, 1398 String potentialDelegatePkg, int potentialDelegateUid) { 1399 synchronized (mPackagePreferences) { 1400 PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); 1401 1402 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, 1403 potentialDelegateUid); 1404 } 1405 } 1406 1407 @VisibleForTesting lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update)1408 void lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update) { 1409 if (original.canBypassDnd() != update.canBypassDnd()) { 1410 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); 1411 } 1412 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) { 1413 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY); 1414 } 1415 if (original.getImportance() != update.getImportance()) { 1416 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 1417 } 1418 if (original.shouldShowLights() != update.shouldShowLights() 1419 || original.getLightColor() != update.getLightColor()) { 1420 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS); 1421 } 1422 if (!Objects.equals(original.getSound(), update.getSound())) { 1423 update.lockFields(NotificationChannel.USER_LOCKED_SOUND); 1424 } 1425 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern()) 1426 || original.shouldVibrate() != update.shouldVibrate()) { 1427 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION); 1428 } 1429 if (original.canShowBadge() != update.canShowBadge()) { 1430 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE); 1431 } 1432 if (original.canBubble() != update.canBubble()) { 1433 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE); 1434 } 1435 } 1436 dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter)1437 public void dump(PrintWriter pw, String prefix, 1438 @NonNull NotificationManagerService.DumpFilter filter) { 1439 pw.print(prefix); 1440 pw.println("per-package config:"); 1441 1442 pw.println("PackagePreferences:"); 1443 synchronized (mPackagePreferences) { 1444 dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences); 1445 } 1446 pw.println("Restored without uid:"); 1447 dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids); 1448 } 1449 dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter)1450 public void dump(ProtoOutputStream proto, 1451 @NonNull NotificationManagerService.DumpFilter filter) { 1452 synchronized (mPackagePreferences) { 1453 dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter, 1454 mPackagePreferences); 1455 } 1456 dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter, 1457 mRestoredWithoutUids); 1458 } 1459 dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences)1460 private static void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, 1461 @NonNull NotificationManagerService.DumpFilter filter, 1462 ArrayMap<String, PackagePreferences> packagePreferences) { 1463 final int N = packagePreferences.size(); 1464 for (int i = 0; i < N; i++) { 1465 final PackagePreferences r = packagePreferences.valueAt(i); 1466 if (filter.matches(r.pkg)) { 1467 pw.print(prefix); 1468 pw.print(" AppSettings: "); 1469 pw.print(r.pkg); 1470 pw.print(" ("); 1471 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid)); 1472 pw.print(')'); 1473 if (r.importance != DEFAULT_IMPORTANCE) { 1474 pw.print(" importance="); 1475 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance)); 1476 } 1477 if (r.priority != DEFAULT_PRIORITY) { 1478 pw.print(" priority="); 1479 pw.print(Notification.priorityToString(r.priority)); 1480 } 1481 if (r.visibility != DEFAULT_VISIBILITY) { 1482 pw.print(" visibility="); 1483 pw.print(Notification.visibilityToString(r.visibility)); 1484 } 1485 if (r.showBadge != DEFAULT_SHOW_BADGE) { 1486 pw.print(" showBadge="); 1487 pw.print(r.showBadge); 1488 } 1489 if (r.defaultAppLockedImportance != DEFAULT_APP_LOCKED_IMPORTANCE) { 1490 pw.print(" defaultAppLocked="); 1491 pw.print(r.defaultAppLockedImportance); 1492 } 1493 if (r.oemLockedImportance != DEFAULT_OEM_LOCKED_IMPORTANCE) { 1494 pw.print(" oemLocked="); 1495 pw.print(r.oemLockedImportance); 1496 } 1497 if (!r.futureOemLockedChannels.isEmpty()) { 1498 pw.print(" futureLockedChannels="); 1499 pw.print(r.futureOemLockedChannels); 1500 } 1501 pw.println(); 1502 for (NotificationChannel channel : r.channels.values()) { 1503 pw.print(prefix); 1504 channel.dump(pw, " ", filter.redact); 1505 } 1506 for (NotificationChannelGroup group : r.groups.values()) { 1507 pw.print(prefix); 1508 pw.print(" "); 1509 pw.print(" "); 1510 pw.println(group); 1511 } 1512 } 1513 } 1514 } 1515 dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences)1516 private static void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, 1517 @NonNull NotificationManagerService.DumpFilter filter, 1518 ArrayMap<String, PackagePreferences> packagePreferences) { 1519 final int N = packagePreferences.size(); 1520 long fToken; 1521 for (int i = 0; i < N; i++) { 1522 final PackagePreferences r = packagePreferences.valueAt(i); 1523 if (filter.matches(r.pkg)) { 1524 fToken = proto.start(fieldId); 1525 1526 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg); 1527 proto.write(RankingHelperProto.RecordProto.UID, r.uid); 1528 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance); 1529 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority); 1530 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility); 1531 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge); 1532 1533 for (NotificationChannel channel : r.channels.values()) { 1534 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS); 1535 } 1536 for (NotificationChannelGroup group : r.groups.values()) { 1537 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS); 1538 } 1539 1540 proto.end(fToken); 1541 } 1542 } 1543 } 1544 dumpJson(NotificationManagerService.DumpFilter filter)1545 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) { 1546 JSONObject ranking = new JSONObject(); 1547 JSONArray PackagePreferencess = new JSONArray(); 1548 try { 1549 ranking.put("noUid", mRestoredWithoutUids.size()); 1550 } catch (JSONException e) { 1551 // pass 1552 } 1553 synchronized (mPackagePreferences) { 1554 final int N = mPackagePreferences.size(); 1555 for (int i = 0; i < N; i++) { 1556 final PackagePreferences r = mPackagePreferences.valueAt(i); 1557 if (filter == null || filter.matches(r.pkg)) { 1558 JSONObject PackagePreferences = new JSONObject(); 1559 try { 1560 PackagePreferences.put("userId", UserHandle.getUserId(r.uid)); 1561 PackagePreferences.put("packageName", r.pkg); 1562 if (r.importance != DEFAULT_IMPORTANCE) { 1563 PackagePreferences.put("importance", 1564 NotificationListenerService.Ranking.importanceToString( 1565 r.importance)); 1566 } 1567 if (r.priority != DEFAULT_PRIORITY) { 1568 PackagePreferences.put("priority", 1569 Notification.priorityToString(r.priority)); 1570 } 1571 if (r.visibility != DEFAULT_VISIBILITY) { 1572 PackagePreferences.put("visibility", 1573 Notification.visibilityToString(r.visibility)); 1574 } 1575 if (r.showBadge != DEFAULT_SHOW_BADGE) { 1576 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge)); 1577 } 1578 JSONArray channels = new JSONArray(); 1579 for (NotificationChannel channel : r.channels.values()) { 1580 channels.put(channel.toJson()); 1581 } 1582 PackagePreferences.put("channels", channels); 1583 JSONArray groups = new JSONArray(); 1584 for (NotificationChannelGroup group : r.groups.values()) { 1585 groups.put(group.toJson()); 1586 } 1587 PackagePreferences.put("groups", groups); 1588 } catch (JSONException e) { 1589 // pass 1590 } 1591 PackagePreferencess.put(PackagePreferences); 1592 } 1593 } 1594 } 1595 try { 1596 ranking.put("PackagePreferencess", PackagePreferencess); 1597 } catch (JSONException e) { 1598 // pass 1599 } 1600 return ranking; 1601 } 1602 1603 /** 1604 * Dump only the ban information as structured JSON for the stats collector. 1605 * 1606 * This is intentionally redundant with {#link dumpJson} because the old 1607 * scraper will expect this format. 1608 * 1609 * @param filter 1610 * @return 1611 */ dumpBansJson(NotificationManagerService.DumpFilter filter)1612 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) { 1613 JSONArray bans = new JSONArray(); 1614 Map<Integer, String> packageBans = getPackageBans(); 1615 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) { 1616 final int userId = UserHandle.getUserId(ban.getKey()); 1617 final String packageName = ban.getValue(); 1618 if (filter == null || filter.matches(packageName)) { 1619 JSONObject banJson = new JSONObject(); 1620 try { 1621 banJson.put("userId", userId); 1622 banJson.put("packageName", packageName); 1623 } catch (JSONException e) { 1624 e.printStackTrace(); 1625 } 1626 bans.put(banJson); 1627 } 1628 } 1629 return bans; 1630 } 1631 getPackageBans()1632 public Map<Integer, String> getPackageBans() { 1633 synchronized (mPackagePreferences) { 1634 final int N = mPackagePreferences.size(); 1635 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N); 1636 for (int i = 0; i < N; i++) { 1637 final PackagePreferences r = mPackagePreferences.valueAt(i); 1638 if (r.importance == IMPORTANCE_NONE) { 1639 packageBans.put(r.uid, r.pkg); 1640 } 1641 } 1642 1643 return packageBans; 1644 } 1645 } 1646 1647 /** 1648 * Dump only the channel information as structured JSON for the stats collector. 1649 * 1650 * This is intentionally redundant with {#link dumpJson} because the old 1651 * scraper will expect this format. 1652 * 1653 * @param filter 1654 * @return 1655 */ dumpChannelsJson(NotificationManagerService.DumpFilter filter)1656 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) { 1657 JSONArray channels = new JSONArray(); 1658 Map<String, Integer> packageChannels = getPackageChannels(); 1659 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) { 1660 final String packageName = channelCount.getKey(); 1661 if (filter == null || filter.matches(packageName)) { 1662 JSONObject channelCountJson = new JSONObject(); 1663 try { 1664 channelCountJson.put("packageName", packageName); 1665 channelCountJson.put("channelCount", channelCount.getValue()); 1666 } catch (JSONException e) { 1667 e.printStackTrace(); 1668 } 1669 channels.put(channelCountJson); 1670 } 1671 } 1672 return channels; 1673 } 1674 getPackageChannels()1675 private Map<String, Integer> getPackageChannels() { 1676 ArrayMap<String, Integer> packageChannels = new ArrayMap<>(); 1677 synchronized (mPackagePreferences) { 1678 for (int i = 0; i < mPackagePreferences.size(); i++) { 1679 final PackagePreferences r = mPackagePreferences.valueAt(i); 1680 int channelCount = 0; 1681 for (int j = 0; j < r.channels.size(); j++) { 1682 if (!r.channels.valueAt(j).isDeleted()) { 1683 channelCount++; 1684 } 1685 } 1686 packageChannels.put(r.pkg, channelCount); 1687 } 1688 } 1689 return packageChannels; 1690 } 1691 1692 /** 1693 * Called when user switches 1694 */ onUserSwitched(int userId)1695 public void onUserSwitched(int userId) { 1696 syncChannelsBypassingDnd(userId); 1697 } 1698 1699 /** 1700 * Called when user is unlocked 1701 */ onUserUnlocked(int userId)1702 public void onUserUnlocked(int userId) { 1703 syncChannelsBypassingDnd(userId); 1704 } 1705 onUserRemoved(int userId)1706 public void onUserRemoved(int userId) { 1707 synchronized (mPackagePreferences) { 1708 int N = mPackagePreferences.size(); 1709 for (int i = N - 1; i >= 0; i--) { 1710 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i); 1711 if (UserHandle.getUserId(PackagePreferences.uid) == userId) { 1712 mPackagePreferences.removeAt(i); 1713 } 1714 } 1715 } 1716 } 1717 onLocaleChanged(Context context, int userId)1718 protected void onLocaleChanged(Context context, int userId) { 1719 synchronized (mPackagePreferences) { 1720 int N = mPackagePreferences.size(); 1721 for (int i = 0; i < N; i++) { 1722 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i); 1723 if (UserHandle.getUserId(PackagePreferences.uid) == userId) { 1724 if (PackagePreferences.channels.containsKey( 1725 NotificationChannel.DEFAULT_CHANNEL_ID)) { 1726 PackagePreferences.channels.get( 1727 NotificationChannel.DEFAULT_CHANNEL_ID).setName( 1728 context.getResources().getString( 1729 R.string.default_notification_channel_label)); 1730 } 1731 } 1732 } 1733 } 1734 } 1735 onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList)1736 public boolean onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, 1737 int[] uidList) { 1738 if (pkgList == null || pkgList.length == 0) { 1739 return false; // nothing to do 1740 } 1741 boolean updated = false; 1742 if (removingPackage) { 1743 // Remove notification settings for uninstalled package 1744 int size = Math.min(pkgList.length, uidList.length); 1745 for (int i = 0; i < size; i++) { 1746 final String pkg = pkgList[i]; 1747 final int uid = uidList[i]; 1748 synchronized (mPackagePreferences) { 1749 mPackagePreferences.remove(packagePreferencesKey(pkg, uid)); 1750 } 1751 mRestoredWithoutUids.remove(pkg); 1752 updated = true; 1753 } 1754 } else { 1755 for (String pkg : pkgList) { 1756 // Package install 1757 final PackagePreferences r = mRestoredWithoutUids.get(pkg); 1758 if (r != null) { 1759 try { 1760 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId); 1761 mRestoredWithoutUids.remove(pkg); 1762 synchronized (mPackagePreferences) { 1763 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r); 1764 } 1765 updated = true; 1766 } catch (PackageManager.NameNotFoundException e) { 1767 // noop 1768 } 1769 } 1770 // Package upgrade 1771 try { 1772 synchronized (mPackagePreferences) { 1773 PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg, 1774 mPm.getPackageUidAsUser(pkg, changeUserId)); 1775 if (fullPackagePreferences != null) { 1776 updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences); 1777 updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences); 1778 } 1779 } 1780 } catch (PackageManager.NameNotFoundException e) { 1781 } 1782 } 1783 } 1784 1785 if (updated) { 1786 updateConfig(); 1787 } 1788 return updated; 1789 } 1790 clearData(String pkg, int uid)1791 public void clearData(String pkg, int uid) { 1792 synchronized (mPackagePreferences) { 1793 PackagePreferences p = getPackagePreferencesLocked(pkg, uid); 1794 if (p != null) { 1795 p.channels = new ArrayMap<>(); 1796 p.groups = new ArrayMap<>(); 1797 p.delegate = null; 1798 p.lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; 1799 p.allowBubble = DEFAULT_ALLOW_BUBBLE; 1800 p.importance = DEFAULT_IMPORTANCE; 1801 p.priority = DEFAULT_PRIORITY; 1802 p.visibility = DEFAULT_VISIBILITY; 1803 p.showBadge = DEFAULT_SHOW_BADGE; 1804 } 1805 } 1806 } 1807 getChannelLog(NotificationChannel channel, String pkg)1808 private LogMaker getChannelLog(NotificationChannel channel, String pkg) { 1809 return new LogMaker( 1810 com.android.internal.logging.nano.MetricsProto.MetricsEvent 1811 .ACTION_NOTIFICATION_CHANNEL) 1812 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE) 1813 .setPackageName(pkg) 1814 .addTaggedData( 1815 com.android.internal.logging.nano.MetricsProto.MetricsEvent 1816 .FIELD_NOTIFICATION_CHANNEL_ID, 1817 channel.getId()) 1818 .addTaggedData( 1819 com.android.internal.logging.nano.MetricsProto.MetricsEvent 1820 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, 1821 channel.getImportance()); 1822 } 1823 getChannelGroupLog(String groupId, String pkg)1824 private LogMaker getChannelGroupLog(String groupId, String pkg) { 1825 return new LogMaker( 1826 com.android.internal.logging.nano.MetricsProto.MetricsEvent 1827 .ACTION_NOTIFICATION_CHANNEL_GROUP) 1828 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE) 1829 .addTaggedData( 1830 com.android.internal.logging.nano.MetricsProto.MetricsEvent 1831 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID, 1832 groupId) 1833 .setPackageName(pkg); 1834 } 1835 updateBubblesEnabled()1836 public void updateBubblesEnabled() { 1837 final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(), 1838 Settings.Global.NOTIFICATION_BUBBLES, 1839 DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1; 1840 if (newValue != mBubblesEnabled) { 1841 mBubblesEnabled = newValue; 1842 updateConfig(); 1843 } 1844 } 1845 bubblesEnabled()1846 public boolean bubblesEnabled() { 1847 return mBubblesEnabled; 1848 } 1849 updateBadgingEnabled()1850 public void updateBadgingEnabled() { 1851 if (mBadgingEnabled == null) { 1852 mBadgingEnabled = new SparseBooleanArray(); 1853 } 1854 boolean changed = false; 1855 // update the cached values 1856 for (int index = 0; index < mBadgingEnabled.size(); index++) { 1857 int userId = mBadgingEnabled.keyAt(index); 1858 final boolean oldValue = mBadgingEnabled.get(userId); 1859 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(), 1860 Settings.Secure.NOTIFICATION_BADGING, 1861 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0; 1862 mBadgingEnabled.put(userId, newValue); 1863 changed |= oldValue != newValue; 1864 } 1865 if (changed) { 1866 updateConfig(); 1867 } 1868 } 1869 badgingEnabled(UserHandle userHandle)1870 public boolean badgingEnabled(UserHandle userHandle) { 1871 int userId = userHandle.getIdentifier(); 1872 if (userId == UserHandle.USER_ALL) { 1873 return false; 1874 } 1875 if (mBadgingEnabled.indexOfKey(userId) < 0) { 1876 mBadgingEnabled.put(userId, 1877 Settings.Secure.getIntForUser(mContext.getContentResolver(), 1878 Settings.Secure.NOTIFICATION_BADGING, 1879 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0); 1880 } 1881 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE); 1882 } 1883 updateConfig()1884 private void updateConfig() { 1885 mRankingHandler.requestSort(); 1886 } 1887 packagePreferencesKey(String pkg, int uid)1888 private static String packagePreferencesKey(String pkg, int uid) { 1889 return pkg + "|" + uid; 1890 } 1891 1892 private static class PackagePreferences { 1893 String pkg; 1894 int uid = UNKNOWN_UID; 1895 int importance = DEFAULT_IMPORTANCE; 1896 int priority = DEFAULT_PRIORITY; 1897 int visibility = DEFAULT_VISIBILITY; 1898 boolean showBadge = DEFAULT_SHOW_BADGE; 1899 boolean allowBubble = DEFAULT_ALLOW_BUBBLE; 1900 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS; 1901 // these fields are loaded on boot from a different source of truth and so are not 1902 // written to notification policy xml 1903 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE; 1904 List<String> futureOemLockedChannels = new ArrayList<>(); 1905 boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE; 1906 1907 Delegate delegate = null; 1908 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); 1909 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>(); 1910 isValidDelegate(String pkg, int uid)1911 public boolean isValidDelegate(String pkg, int uid) { 1912 return delegate != null && delegate.isAllowed(pkg, uid); 1913 } 1914 } 1915 1916 private static class Delegate { 1917 static final boolean DEFAULT_ENABLED = true; 1918 static final boolean DEFAULT_USER_ALLOWED = true; 1919 String mPkg; 1920 int mUid = UNKNOWN_UID; 1921 boolean mEnabled = DEFAULT_ENABLED; 1922 boolean mUserAllowed = DEFAULT_USER_ALLOWED; 1923 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed)1924 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) { 1925 mPkg = pkg; 1926 mUid = uid; 1927 mEnabled = enabled; 1928 mUserAllowed = userAllowed; 1929 } 1930 isAllowed(String pkg, int uid)1931 public boolean isAllowed(String pkg, int uid) { 1932 if (pkg == null || uid == UNKNOWN_UID) { 1933 return false; 1934 } 1935 return pkg.equals(mPkg) 1936 && uid == mUid 1937 && (mUserAllowed && mEnabled); 1938 } 1939 } 1940 } 1941