1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.notification; 17 18 import static android.app.NotificationManager.IMPORTANCE_NONE; 19 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 20 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED; 21 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; 22 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; 23 24 import static com.android.server.notification.Flags.notificationHideUnusedChannels; 25 26 import android.annotation.FlaggedApi; 27 import android.app.Flags; 28 import android.app.INotificationManager; 29 import android.app.NotificationChannel; 30 import android.app.NotificationChannelGroup; 31 import android.app.NotificationHistory; 32 import android.app.NotificationManager; 33 import android.app.usage.IUsageStatsManager; 34 import android.app.usage.UsageEvents; 35 import android.companion.ICompanionDeviceManager; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.ApplicationInfo; 40 import android.content.pm.LauncherApps; 41 import android.content.pm.PackageInfo; 42 import android.content.pm.PackageManager; 43 import android.content.pm.ParceledListSlice; 44 import android.content.pm.ShortcutInfo; 45 import android.content.pm.ShortcutManager; 46 import android.graphics.drawable.Drawable; 47 import android.os.Build; 48 import android.os.RemoteException; 49 import android.os.ServiceManager; 50 import android.os.UserHandle; 51 import android.service.notification.Adjustment; 52 import android.service.notification.ConversationChannelWrapper; 53 import android.service.notification.NotificationListenerFilter; 54 import android.text.format.DateUtils; 55 import android.util.IconDrawableFactory; 56 import android.util.Log; 57 58 import androidx.annotation.NonNull; 59 import androidx.annotation.VisibleForTesting; 60 61 import com.android.internal.util.CollectionUtils; 62 import com.android.settings.R; 63 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 64 import com.android.settingslib.bluetooth.LocalBluetoothManager; 65 import com.android.settingslib.notification.ConversationIconFactory; 66 import com.android.settingslib.utils.StringUtil; 67 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.Collection; 71 import java.util.Collections; 72 import java.util.HashMap; 73 import java.util.HashSet; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.Objects; 77 import java.util.Set; 78 79 public class NotificationBackend { 80 private static final String TAG = "NotificationBackend"; 81 82 static IUsageStatsManager sUsageStatsManager = IUsageStatsManager.Stub.asInterface( 83 ServiceManager.getService(Context.USAGE_STATS_SERVICE)); 84 private static final int DAYS_TO_CHECK = 7; 85 static INotificationManager sINM = INotificationManager.Stub.asInterface( 86 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 87 loadAppRow(Context context, PackageManager pm, ApplicationInfo app)88 public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app) { 89 final AppRow row = new AppRow(); 90 if (notificationHideUnusedChannels()) { 91 row.showAllChannels = false; 92 } 93 row.pkg = app.packageName; 94 row.uid = app.uid; 95 try { 96 row.label = app.loadLabel(pm); 97 } catch (Throwable t) { 98 Log.e(TAG, "Error loading application label for " + row.pkg, t); 99 row.label = row.pkg; 100 } 101 row.icon = IconDrawableFactory.newInstance(context).getBadgedIcon(app); 102 row.banned = getNotificationsBanned(row.pkg, row.uid); 103 row.showBadge = canShowBadge(row.pkg, row.uid); 104 row.bubblePreference = getBubblePreference(row.pkg, row.uid); 105 row.userId = UserHandle.getUserId(row.uid); 106 row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid); 107 row.channelCount = getChannelCount(row.pkg, row.uid); 108 recordAggregatedUsageEvents(context, row); 109 if (Flags.uiRichOngoing()) { 110 row.canBePromoted = canBePromoted(row.pkg, row.uid); 111 } 112 return row; 113 } 114 loadAppRow(Context context, PackageManager pm, PackageInfo app)115 public AppRow loadAppRow(Context context, PackageManager pm, PackageInfo app) { 116 final AppRow row = loadAppRow(context, pm, app.applicationInfo); 117 recordCanBeBlocked(app, row); 118 return row; 119 } 120 recordCanBeBlocked(PackageInfo app, AppRow row)121 void recordCanBeBlocked(PackageInfo app, AppRow row) { 122 try { 123 row.systemApp = row.lockedImportance = 124 sINM.isImportanceLocked(app.packageName, app.applicationInfo.uid); 125 } catch (RemoteException e) { 126 Log.w(TAG, "Error calling NMS", e); 127 } 128 129 // if the app targets T but has not requested the permission, we cannot change the 130 // permission state 131 if (app.applicationInfo.targetSdkVersion > Build.VERSION_CODES.S_V2) { 132 if (app.requestedPermissions == null || Arrays.stream(app.requestedPermissions) 133 .noneMatch(p -> p.equals(android.Manifest.permission.POST_NOTIFICATIONS))) { 134 row.lockedImportance = true; 135 row.permissionStateLocked = true; 136 } 137 } 138 } 139 getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, String pkg, int userId)140 static public CharSequence getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, 141 String pkg, int userId) { 142 if (cdm == null) { 143 return ""; 144 } 145 boolean multiple = false; 146 StringBuilder sb = new StringBuilder(); 147 148 try { 149 List<String> associatedMacAddrs = CollectionUtils.mapNotNull( 150 cdm.getAssociations(pkg, userId), 151 a -> a.isSelfManaged() ? null : a.getDeviceMacAddress().toString()); 152 if (associatedMacAddrs != null) { 153 for (String assocMac : associatedMacAddrs) { 154 final Collection<CachedBluetoothDevice> cachedDevices = 155 lbm.getCachedDeviceManager().getCachedDevicesCopy(); 156 for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) { 157 if (Objects.equals(assocMac, cachedBluetoothDevice.getAddress())) { 158 if (multiple) { 159 sb.append(", "); 160 } else { 161 multiple = true; 162 } 163 sb.append(cachedBluetoothDevice.getName()); 164 } 165 } 166 } 167 } 168 } catch (RemoteException e) { 169 Log.w(TAG, "Error calling CDM", e); 170 } 171 return sb.toString(); 172 } 173 enableSwitch(Context context, ApplicationInfo app)174 public boolean enableSwitch(Context context, ApplicationInfo app) { 175 try { 176 PackageInfo info = context.getPackageManager().getPackageInfo( 177 app.packageName, PackageManager.GET_PERMISSIONS); 178 final AppRow row = new AppRow(); 179 recordCanBeBlocked(info, row); 180 boolean systemBlockable = !row.systemApp || (row.systemApp && row.banned); 181 return systemBlockable && !row.lockedImportance; 182 } catch (PackageManager.NameNotFoundException e) { 183 e.printStackTrace(); 184 } 185 return false; 186 } 187 getNotificationsBanned(String pkg, int uid)188 public boolean getNotificationsBanned(String pkg, int uid) { 189 try { 190 final boolean enabled = sINM.areNotificationsEnabledForPackage(pkg, uid); 191 return !enabled; 192 } catch (Exception e) { 193 Log.w(TAG, "Error calling NoMan", e); 194 return false; 195 } 196 } 197 setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled)198 public boolean setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { 199 try { 200 if (onlyHasDefaultChannel(pkg, uid)) { 201 NotificationChannel defaultChannel = 202 getChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, null); 203 defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE); 204 updateChannel(pkg, uid, defaultChannel); 205 } 206 sINM.setNotificationsEnabledForPackage(pkg, uid, enabled); 207 return true; 208 } catch (Exception e) { 209 Log.w(TAG, "Error calling NoMan", e); 210 return false; 211 } 212 } 213 canShowBadge(String pkg, int uid)214 public boolean canShowBadge(String pkg, int uid) { 215 try { 216 return sINM.canShowBadge(pkg, uid); 217 } catch (Exception e) { 218 Log.w(TAG, "Error calling NoMan", e); 219 return false; 220 } 221 } 222 setShowBadge(String pkg, int uid, boolean showBadge)223 public boolean setShowBadge(String pkg, int uid, boolean showBadge) { 224 try { 225 sINM.setShowBadge(pkg, uid, showBadge); 226 return true; 227 } catch (Exception e) { 228 Log.w(TAG, "Error calling NoMan", e); 229 return false; 230 } 231 } 232 getBubblePreference(String pkg, int uid)233 public int getBubblePreference(String pkg, int uid) { 234 try { 235 return sINM.getBubblePreferenceForPackage(pkg, uid); 236 } catch (Exception e) { 237 Log.w(TAG, "Error calling NoMan", e); 238 return -1; 239 } 240 } 241 setAllowBubbles(String pkg, int uid, int preference)242 public boolean setAllowBubbles(String pkg, int uid, int preference) { 243 try { 244 sINM.setBubblesAllowed(pkg, uid, preference); 245 return true; 246 } catch (Exception e) { 247 Log.w(TAG, "Error calling NoMan", e); 248 return false; 249 } 250 } 251 getChannel(String pkg, int uid, String channelId)252 public NotificationChannel getChannel(String pkg, int uid, String channelId) { 253 return getChannel(pkg, uid, channelId, null); 254 } 255 getChannel(String pkg, int uid, String channelId, String conversationId)256 public NotificationChannel getChannel(String pkg, int uid, String channelId, 257 String conversationId) { 258 if (channelId == null) { 259 return null; 260 } 261 try { 262 return sINM.getNotificationChannelForPackage(pkg, uid, channelId, conversationId, true); 263 } catch (Exception e) { 264 Log.w(TAG, "Error calling NoMan", e); 265 return null; 266 } 267 } 268 getGroup(String pkg, int uid, String groupId)269 public NotificationChannelGroup getGroup(String pkg, int uid, String groupId) { 270 if (groupId == null) { 271 return null; 272 } 273 try { 274 return sINM.getNotificationChannelGroupForPackage(groupId, pkg, uid); 275 } catch (Exception e) { 276 Log.w(TAG, "Error calling NoMan", e); 277 return null; 278 } 279 } 280 getGroups(String pkg, int uid)281 public ParceledListSlice<NotificationChannelGroup> getGroups(String pkg, int uid) { 282 try { 283 return sINM.getNotificationChannelGroupsForPackage(pkg, uid, false); 284 } catch (Exception e) { 285 Log.w(TAG, "Error calling NoMan", e); 286 return ParceledListSlice.emptyList(); 287 } 288 } 289 getGroupsWithRecentBlockedFilter(String pkg, int uid)290 public ParceledListSlice<NotificationChannelGroup> getGroupsWithRecentBlockedFilter(String pkg, 291 int uid) { 292 try { 293 return sINM.getRecentBlockedNotificationChannelGroupsForPackage(pkg, uid); 294 } catch (Exception e) { 295 Log.w(TAG, "Error calling NoMan", e); 296 return ParceledListSlice.emptyList(); 297 } 298 } 299 getConversations(String pkg, int uid)300 public ParceledListSlice<ConversationChannelWrapper> getConversations(String pkg, int uid) { 301 try { 302 return sINM.getConversationsForPackage(pkg, uid); 303 } catch (Exception e) { 304 Log.w(TAG, "Error calling NoMan", e); 305 return ParceledListSlice.emptyList(); 306 } 307 } 308 getConversations(boolean onlyImportant)309 public ParceledListSlice<ConversationChannelWrapper> getConversations(boolean onlyImportant) { 310 try { 311 return sINM.getConversations(onlyImportant); 312 } catch (Exception e) { 313 Log.w(TAG, "Error calling NoMan", e); 314 return ParceledListSlice.emptyList(); 315 } 316 } 317 hasSentValidMsg(String pkg, int uid)318 public boolean hasSentValidMsg(String pkg, int uid) { 319 try { 320 return sINM.hasSentValidMsg(pkg, uid); 321 } catch (Exception e) { 322 Log.w(TAG, "Error calling NoMan", e); 323 return false; 324 } 325 } 326 isInInvalidMsgState(String pkg, int uid)327 public boolean isInInvalidMsgState(String pkg, int uid) { 328 try { 329 return sINM.isInInvalidMsgState(pkg, uid); 330 } catch (Exception e) { 331 Log.w(TAG, "Error calling NoMan", e); 332 return false; 333 } 334 } 335 hasUserDemotedInvalidMsgApp(String pkg, int uid)336 public boolean hasUserDemotedInvalidMsgApp(String pkg, int uid) { 337 try { 338 return sINM.hasUserDemotedInvalidMsgApp(pkg, uid); 339 } catch (Exception e) { 340 Log.w(TAG, "Error calling NoMan", e); 341 return false; 342 } 343 } 344 setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted)345 public void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted) { 346 try { 347 sINM.setInvalidMsgAppDemoted(pkg, uid, isDemoted); 348 } catch (Exception e) { 349 Log.w(TAG, "Error calling NoMan", e); 350 } 351 } 352 hasSentValidBubble(String pkg, int uid)353 public boolean hasSentValidBubble(String pkg, int uid) { 354 try { 355 return sINM.hasSentValidBubble(pkg, uid); 356 } catch (Exception e) { 357 Log.w(TAG, "Error calling NoMan", e); 358 return false; 359 } 360 } 361 362 /** 363 * Returns all notification channels associated with the package and uid that will bypass DND 364 */ getNotificationChannelsBypassingDnd(String pkg, int uid)365 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg, 366 int uid) { 367 try { 368 return sINM.getNotificationChannelsBypassingDnd(pkg, uid); 369 } catch (Exception e) { 370 Log.w(TAG, "Error calling NoMan", e); 371 return ParceledListSlice.emptyList(); 372 } 373 } 374 375 /** 376 * Returns a set of all apps that have any notification channels (not including deleted ones). 377 */ 378 @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS) getPackagesWithAnyChannels(int userId)379 public @NonNull Set<String> getPackagesWithAnyChannels(int userId) { 380 try { 381 List<String> packages = sINM.getPackagesWithAnyChannels(userId); 382 return new HashSet<>(packages); 383 } catch (Exception e) { 384 Log.w(TAG, "Error calling NoMan", e); 385 return Collections.EMPTY_SET; 386 } 387 } 388 updateChannel(String pkg, int uid, NotificationChannel channel)389 public void updateChannel(String pkg, int uid, NotificationChannel channel) { 390 try { 391 sINM.updateNotificationChannelForPackage(pkg, uid, channel); 392 } catch (Exception e) { 393 Log.w(TAG, "Error calling NoMan", e); 394 } 395 } 396 updateChannelGroup(String pkg, int uid, NotificationChannelGroup group)397 public void updateChannelGroup(String pkg, int uid, NotificationChannelGroup group) { 398 try { 399 sINM.updateNotificationChannelGroupForPackage(pkg, uid, group); 400 } catch (Exception e) { 401 Log.w(TAG, "Error calling NoMan", e); 402 } 403 } 404 getDeletedChannelCount(String pkg, int uid)405 public int getDeletedChannelCount(String pkg, int uid) { 406 try { 407 return sINM.getDeletedChannelCount(pkg, uid); 408 } catch (Exception e) { 409 Log.w(TAG, "Error calling NoMan", e); 410 return 0; 411 } 412 } 413 getBlockedChannelCount(String pkg, int uid)414 public int getBlockedChannelCount(String pkg, int uid) { 415 try { 416 return sINM.getBlockedChannelCount(pkg, uid); 417 } catch (Exception e) { 418 Log.w(TAG, "Error calling NoMan", e); 419 return 0; 420 } 421 } 422 onlyHasDefaultChannel(String pkg, int uid)423 public boolean onlyHasDefaultChannel(String pkg, int uid) { 424 try { 425 return sINM.onlyHasDefaultChannel(pkg, uid); 426 } catch (Exception e) { 427 Log.w(TAG, "Error calling NoMan", e); 428 return false; 429 } 430 } 431 getChannelCount(String pkg, int uid)432 public int getChannelCount(String pkg, int uid) { 433 try { 434 return sINM.getNumNotificationChannelsForPackage(pkg, uid, false); 435 } catch (Exception e) { 436 Log.w(TAG, "Error calling NoMan", e); 437 return 0; 438 } 439 } 440 shouldHideSilentStatusBarIcons(Context context)441 public boolean shouldHideSilentStatusBarIcons(Context context) { 442 try { 443 return sINM.shouldHideSilentStatusIcons(context.getPackageName()); 444 } catch (Exception e) { 445 Log.w(TAG, "Error calling NoMan", e); 446 return false; 447 } 448 } 449 setHideSilentStatusIcons(boolean hide)450 public void setHideSilentStatusIcons(boolean hide) { 451 try { 452 sINM.setHideSilentStatusIcons(hide); 453 } catch (Exception e) { 454 Log.w(TAG, "Error calling NoMan", e); 455 } 456 } 457 getAllowedAssistantAdjustments(String pkg)458 public List<String> getAllowedAssistantAdjustments(String pkg) { 459 try { 460 return sINM.getAllowedAssistantAdjustments(pkg); 461 } catch (Exception e) { 462 Log.w(TAG, "Error calling NoMan", e); 463 } 464 return new ArrayList<>(); 465 } 466 showSilentInStatusBar(String pkg)467 public boolean showSilentInStatusBar(String pkg) { 468 try { 469 return !sINM.shouldHideSilentStatusIcons(pkg); 470 } catch (Exception e) { 471 Log.w(TAG, "Error calling NoMan", e); 472 } 473 return false; 474 } 475 getNotificationHistory(String pkg, String attributionTag)476 public NotificationHistory getNotificationHistory(String pkg, String attributionTag) { 477 try { 478 return sINM.getNotificationHistory(pkg, attributionTag); 479 } catch (Exception e) { 480 Log.w(TAG, "Error calling NoMan", e); 481 } 482 return new NotificationHistory(); 483 } 484 recordAggregatedUsageEvents(Context context, AppRow appRow)485 protected void recordAggregatedUsageEvents(Context context, AppRow appRow) { 486 long now = System.currentTimeMillis(); 487 long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK); 488 UsageEvents events = null; 489 try { 490 events = sUsageStatsManager.queryEventsForPackageForUser( 491 startTime, now, appRow.userId, appRow.pkg, context.getPackageName()); 492 } catch (RemoteException e) { 493 e.printStackTrace(); 494 } 495 recordAggregatedUsageEvents(events, appRow); 496 } 497 recordAggregatedUsageEvents(UsageEvents events, AppRow appRow)498 protected void recordAggregatedUsageEvents(UsageEvents events, AppRow appRow) { 499 appRow.sentByChannel = new HashMap<>(); 500 appRow.sentByApp = new NotificationsSentState(); 501 if (events != null) { 502 UsageEvents.Event event = new UsageEvents.Event(); 503 while (events.hasNextEvent()) { 504 events.getNextEvent(event); 505 506 if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { 507 String channelId = event.mNotificationChannelId; 508 if (channelId != null) { 509 NotificationsSentState stats = appRow.sentByChannel.get(channelId); 510 if (stats == null) { 511 stats = new NotificationsSentState(); 512 appRow.sentByChannel.put(channelId, stats); 513 } 514 if (event.getTimeStamp() > stats.lastSent) { 515 stats.lastSent = event.getTimeStamp(); 516 appRow.sentByApp.lastSent = event.getTimeStamp(); 517 } 518 stats.sentCount++; 519 appRow.sentByApp.sentCount++; 520 calculateAvgSentCounts(stats); 521 } 522 } 523 524 } 525 calculateAvgSentCounts(appRow.sentByApp); 526 } 527 } 528 getSentSummary(Context context, NotificationsSentState state, boolean sortByRecency)529 public static CharSequence getSentSummary(Context context, NotificationsSentState state, 530 boolean sortByRecency) { 531 if (state == null) { 532 return null; 533 } 534 if (sortByRecency) { 535 if (state.lastSent == 0) { 536 return context.getString(R.string.notifications_sent_never); 537 } 538 return StringUtil.formatRelativeTime( 539 context, System.currentTimeMillis() - state.lastSent, true); 540 } else { 541 if (state.avgSentDaily > 0) { 542 return StringUtil.getIcuPluralsString(context, state.avgSentDaily, 543 R.string.notifications_sent_daily); 544 } 545 return StringUtil.getIcuPluralsString(context, state.avgSentWeekly, 546 R.string.notifications_sent_weekly); 547 } 548 } 549 calculateAvgSentCounts(NotificationsSentState stats)550 private void calculateAvgSentCounts(NotificationsSentState stats) { 551 if (stats != null) { 552 stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK); 553 if (stats.sentCount < DAYS_TO_CHECK) { 554 stats.avgSentWeekly = stats.sentCount; 555 } 556 } 557 } 558 getAllowedNotificationAssistant()559 public ComponentName getAllowedNotificationAssistant() { 560 try { 561 return sINM.getAllowedNotificationAssistant(); 562 } catch (Exception e) { 563 Log.w(TAG, "Error calling NoMan", e); 564 return null; 565 } 566 } 567 getDefaultNotificationAssistant()568 public ComponentName getDefaultNotificationAssistant() { 569 try { 570 return sINM.getDefaultNotificationAssistant(); 571 } catch (Exception e) { 572 Log.w(TAG, "Error calling NoMan", e); 573 return null; 574 } 575 } 576 setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig)577 public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) { 578 try { 579 sINM.setNASMigrationDoneAndResetDefault(userId, loadFromConfig); 580 } catch (Exception e) { 581 Log.w(TAG, "Error calling NoMan", e); 582 } 583 } 584 setNotificationAssistantGranted(ComponentName cn)585 public boolean setNotificationAssistantGranted(ComponentName cn) { 586 try { 587 sINM.setNotificationAssistantAccessGranted(cn, true); 588 if (cn == null) { 589 return sINM.getAllowedNotificationAssistant() == null; 590 } else { 591 return cn.equals(sINM.getAllowedNotificationAssistant()); 592 } 593 } catch (Exception e) { 594 Log.w(TAG, "Error calling NoMan", e); 595 return false; 596 } 597 } 598 createConversationNotificationChannel(String pkg, int uid, NotificationChannel parent, String conversationId)599 public void createConversationNotificationChannel(String pkg, int uid, 600 NotificationChannel parent, String conversationId) { 601 try { 602 sINM.createConversationNotificationChannelForPackage(pkg, uid, parent, conversationId); 603 } catch (Exception e) { 604 Log.w(TAG, "Error calling NoMan", e); 605 } 606 } 607 getConversationInfo(Context context, String pkg, int uid, String id)608 public ShortcutInfo getConversationInfo(Context context, String pkg, int uid, String id) { 609 LauncherApps la = context.getSystemService(LauncherApps.class); 610 611 LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() 612 .setPackage(pkg) 613 .setQueryFlags(FLAG_MATCH_DYNAMIC 614 | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER | FLAG_MATCH_CACHED) 615 .setShortcutIds(Arrays.asList(id)); 616 List<ShortcutInfo> shortcuts = la.getShortcuts( 617 query, UserHandle.of(UserHandle.getUserId(uid))); 618 if (shortcuts != null && !shortcuts.isEmpty()) { 619 return shortcuts.get(0); 620 } 621 return null; 622 } 623 getConversationDrawable(Context context, ShortcutInfo info, String pkg, int uid, boolean important)624 public Drawable getConversationDrawable(Context context, ShortcutInfo info, String pkg, 625 int uid, boolean important) { 626 if (info == null) { 627 return null; 628 } 629 ConversationIconFactory iconFactory = new ConversationIconFactory(context, 630 context.getSystemService(LauncherApps.class), 631 context.getPackageManager(), 632 IconDrawableFactory.newInstance(context, false), 633 context.getResources().getDimensionPixelSize( 634 R.dimen.conversation_icon_size)); 635 return iconFactory.getConversationDrawable(info, pkg, uid, important); 636 } 637 requestPinShortcut(Context context, ShortcutInfo shortcutInfo)638 public void requestPinShortcut(Context context, ShortcutInfo shortcutInfo) { 639 ShortcutManager sm = context.getSystemService(ShortcutManager.class); 640 sm.requestPinShortcut(shortcutInfo, null); 641 } 642 resetNotificationImportance()643 public void resetNotificationImportance() { 644 try { 645 sINM.unlockAllNotificationChannels(); 646 } catch (Exception e) { 647 Log.w(TAG, "Error calling NoMan", e); 648 } 649 } 650 getListenerFilter(ComponentName cn, int userId)651 public NotificationListenerFilter getListenerFilter(ComponentName cn, int userId) { 652 NotificationListenerFilter nlf = null; 653 try { 654 nlf = sINM.getListenerFilter(cn, userId); 655 } catch (Exception e) { 656 Log.w(TAG, "Error calling NoMan", e); 657 } 658 return nlf != null ? nlf : new NotificationListenerFilter(); 659 } 660 setListenerFilter(ComponentName cn, int userId, NotificationListenerFilter nlf)661 public void setListenerFilter(ComponentName cn, int userId, NotificationListenerFilter nlf) { 662 try { 663 sINM.setListenerFilter(cn, userId, nlf); 664 } catch (Exception e) { 665 Log.w(TAG, "Error calling NoMan", e); 666 } 667 } 668 isNotificationListenerAccessGranted(ComponentName cn)669 public boolean isNotificationListenerAccessGranted(ComponentName cn) { 670 try { 671 return sINM.isNotificationListenerAccessGranted(cn); 672 } catch (Exception e) { 673 Log.w(TAG, "Error calling NoMan", e); 674 } 675 return false; 676 } 677 isNotificationBundlingSupported()678 public boolean isNotificationBundlingSupported() { 679 try { 680 return !sINM.getUnsupportedAdjustmentTypes().contains(Adjustment.KEY_TYPE); 681 } catch (Exception e) { 682 Log.w(TAG, "Error calling NoMan", e); 683 } 684 return false; 685 } 686 isNotificationBundlingEnabled(Context context)687 public boolean isNotificationBundlingEnabled(Context context) { 688 try { 689 return sINM.getAllowedAssistantAdjustments(context.getPackageName()) 690 .contains(Adjustment.KEY_TYPE); 691 } catch (Exception e) { 692 Log.w(TAG, "Error calling NoMan", e); 693 } 694 return false; 695 } 696 setNotificationBundlingEnabled(boolean enabled)697 public void setNotificationBundlingEnabled(boolean enabled) { 698 try { 699 if (enabled) { 700 sINM.allowAssistantAdjustment(Adjustment.KEY_TYPE); 701 } else { 702 sINM.disallowAssistantAdjustment(Adjustment.KEY_TYPE); 703 } 704 } catch (Exception e) { 705 Log.w(TAG, "Error calling NoMan", e); 706 } 707 } 708 isNotificationSummarizationSupported()709 public boolean isNotificationSummarizationSupported() { 710 try { 711 return !sINM.getUnsupportedAdjustmentTypes().contains(Adjustment.KEY_SUMMARIZATION); 712 } catch (Exception e) { 713 Log.w(TAG, "Error calling NoMan", e); 714 } 715 return false; 716 } 717 isNotificationSummarizationEnabled(Context context)718 public boolean isNotificationSummarizationEnabled(Context context) { 719 try { 720 return sINM.getAllowedAssistantAdjustments(context.getPackageName()) 721 .contains(Adjustment.KEY_SUMMARIZATION); 722 } catch (Exception e) { 723 Log.w(TAG, "Error calling NoMan", e); 724 } 725 return false; 726 } 727 setNotificationSummarizationEnabled(boolean enabled)728 public void setNotificationSummarizationEnabled(boolean enabled) { 729 try { 730 if (enabled) { 731 sINM.allowAssistantAdjustment(Adjustment.KEY_SUMMARIZATION); 732 } else { 733 sINM.disallowAssistantAdjustment(Adjustment.KEY_SUMMARIZATION); 734 } 735 } catch (Exception e) { 736 Log.w(TAG, "Error calling NoMan", e); 737 } 738 } 739 isBundleTypeApproved(@djustment.Types int type)740 public boolean isBundleTypeApproved(@Adjustment.Types int type) { 741 try { 742 int[] approved = sINM.getAllowedAdjustmentKeyTypes(); 743 for (int approvedType : approved) { 744 if (type == approvedType) { 745 return true; 746 } 747 } 748 } catch (Exception e) { 749 Log.w(TAG, "Error calling NoMan", e); 750 } 751 return false; 752 } 753 getAllowedBundleTypes()754 public Set<Integer> getAllowedBundleTypes() { 755 try { 756 Set<Integer> allowed = new HashSet<>(); 757 for (int type : sINM.getAllowedAdjustmentKeyTypes()) { 758 allowed.add(type); 759 } 760 return allowed; 761 } catch (Exception e) { 762 Log.w(TAG, "Error calling NoMan", e); 763 return new HashSet<>(); 764 } 765 } 766 setBundleTypeState(@djustment.Types int type, boolean enabled)767 public void setBundleTypeState(@Adjustment.Types int type, boolean enabled) { 768 try { 769 sINM.setAssistantAdjustmentKeyTypeState(type, enabled); 770 } catch (Exception e) { 771 Log.w(TAG, "Error calling NoMan", e); 772 } 773 } 774 775 /** 776 * Retrieves whether the app with given package and uid is permitted to post promoted 777 * notifications. 778 */ canBePromoted(String pkg, int uid)779 public boolean canBePromoted(String pkg, int uid) { 780 try { 781 return sINM.appCanBePromoted(pkg, uid); 782 } catch (Exception e) { 783 Log.w(TAG, "Error calling NoMan", e); 784 return false; 785 } 786 } 787 788 /** 789 * Sets whether the app with given package and uid is permitted to post promoted notifications. 790 */ setCanBePromoted(String pkg, int uid, boolean allowed)791 public void setCanBePromoted(String pkg, int uid, boolean allowed) { 792 // We shouldn't get here with the flag off, but just in case, do nothing. 793 if (!Flags.uiRichOngoing()) { 794 Log.wtf(TAG, "tried to setCanBePromoted without flag on"); 795 return; 796 } 797 try { 798 sINM.setCanBePromoted(pkg, uid, allowed, /* fromUser= */ true); 799 } catch (Exception e) { 800 Log.w(TAG, "Error calling NoMan", e); 801 } 802 } 803 getAdjustmentDeniedPackages(String key)804 public @NonNull String[] getAdjustmentDeniedPackages(String key) { 805 try { 806 return sINM.getAdjustmentDeniedPackages(key); 807 } catch (Exception e) { 808 Log.w(TAG, "Error calling NoMan", e); 809 return new String[]{}; 810 } 811 } 812 setAdjustmentSupportedForPackage(String key, String pkg, boolean enabled)813 public @NonNull void setAdjustmentSupportedForPackage(String key, String pkg, boolean enabled) { 814 try { 815 sINM.setAdjustmentSupportedForPackage(key, pkg, enabled); 816 } catch (Exception e) { 817 Log.w(TAG, "Error calling NoMan", e); 818 } 819 } 820 821 @VisibleForTesting setNm(INotificationManager inm)822 void setNm(INotificationManager inm) { 823 sINM = inm; 824 } 825 826 /** 827 * NotificationsSentState contains how often an app sends notifications and how recently it sent 828 * one. 829 */ 830 public static class NotificationsSentState { 831 public int avgSentDaily = 0; 832 public int avgSentWeekly = 0; 833 public long lastSent = 0; 834 public int sentCount = 0; 835 } 836 837 static class Row { 838 public String section; 839 } 840 841 public static class AppRow extends Row { 842 public String pkg; 843 public int uid; 844 public Drawable icon; 845 public CharSequence label; 846 public Intent settingsIntent; 847 public boolean banned; 848 public boolean first; // first app in section 849 public boolean systemApp; 850 public boolean lockedImportance; 851 public boolean showBadge; 852 // For apps target T but have not but has not requested the permission 853 // we cannot change the permission state 854 public boolean permissionStateLocked; 855 public int bubblePreference = NotificationManager.BUBBLE_PREFERENCE_NONE; 856 public int userId; 857 public int blockedChannelCount; 858 public int channelCount; 859 public Map<String, NotificationsSentState> sentByChannel; 860 public NotificationsSentState sentByApp; 861 public boolean showAllChannels = true; 862 public boolean canBePromoted; 863 } 864 } 865