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