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 android.app.INotificationManager; 25 import android.app.NotificationChannel; 26 import android.app.NotificationChannelGroup; 27 import android.app.NotificationHistory; 28 import android.app.NotificationManager; 29 import android.app.usage.IUsageStatsManager; 30 import android.app.usage.UsageEvents; 31 import android.companion.ICompanionDeviceManager; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.LauncherApps; 37 import android.content.pm.PackageInfo; 38 import android.content.pm.PackageManager; 39 import android.content.pm.ParceledListSlice; 40 import android.content.pm.ShortcutInfo; 41 import android.content.pm.ShortcutManager; 42 import android.graphics.drawable.Drawable; 43 import android.os.Build; 44 import android.os.RemoteException; 45 import android.os.ServiceManager; 46 import android.os.UserHandle; 47 import android.service.notification.ConversationChannelWrapper; 48 import android.service.notification.NotificationListenerFilter; 49 import android.text.format.DateUtils; 50 import android.util.IconDrawableFactory; 51 import android.util.Log; 52 53 import androidx.annotation.VisibleForTesting; 54 55 import com.android.internal.util.CollectionUtils; 56 import com.android.settings.R; 57 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 58 import com.android.settingslib.bluetooth.LocalBluetoothManager; 59 import com.android.settingslib.notification.ConversationIconFactory; 60 import com.android.settingslib.utils.StringUtil; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.Collection; 65 import java.util.HashMap; 66 import java.util.List; 67 import java.util.Map; 68 import java.util.Objects; 69 70 public class NotificationBackend { 71 private static final String TAG = "NotificationBackend"; 72 73 static IUsageStatsManager sUsageStatsManager = IUsageStatsManager.Stub.asInterface( 74 ServiceManager.getService(Context.USAGE_STATS_SERVICE)); 75 private static final int DAYS_TO_CHECK = 7; 76 static INotificationManager sINM = INotificationManager.Stub.asInterface( 77 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 78 loadAppRow(Context context, PackageManager pm, ApplicationInfo app)79 public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app) { 80 final AppRow row = new AppRow(); 81 row.pkg = app.packageName; 82 row.uid = app.uid; 83 try { 84 row.label = app.loadLabel(pm); 85 } catch (Throwable t) { 86 Log.e(TAG, "Error loading application label for " + row.pkg, t); 87 row.label = row.pkg; 88 } 89 row.icon = IconDrawableFactory.newInstance(context).getBadgedIcon(app); 90 row.banned = getNotificationsBanned(row.pkg, row.uid); 91 row.showBadge = canShowBadge(row.pkg, row.uid); 92 row.bubblePreference = getBubblePreference(row.pkg, row.uid); 93 row.userId = UserHandle.getUserId(row.uid); 94 row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid); 95 row.channelCount = getChannelCount(row.pkg, row.uid); 96 recordAggregatedUsageEvents(context, row); 97 return row; 98 } 99 loadAppRow(Context context, PackageManager pm, PackageInfo app)100 public AppRow loadAppRow(Context context, PackageManager pm, PackageInfo app) { 101 final AppRow row = loadAppRow(context, pm, app.applicationInfo); 102 recordCanBeBlocked(app, row); 103 return row; 104 } 105 recordCanBeBlocked(PackageInfo app, AppRow row)106 void recordCanBeBlocked(PackageInfo app, AppRow row) { 107 try { 108 row.systemApp = row.lockedImportance = 109 sINM.isImportanceLocked(app.packageName, app.applicationInfo.uid); 110 } catch (RemoteException e) { 111 Log.w(TAG, "Error calling NMS", e); 112 } 113 114 // if the app targets T but has not requested the permission, we cannot change the 115 // permission state 116 if (app.applicationInfo.targetSdkVersion > Build.VERSION_CODES.S_V2) { 117 if (app.requestedPermissions == null || Arrays.stream(app.requestedPermissions) 118 .noneMatch(p -> p.equals(android.Manifest.permission.POST_NOTIFICATIONS))) { 119 row.lockedImportance = true; 120 row.permissionStateLocked = true; 121 } 122 } 123 } 124 getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, String pkg, int userId)125 static public CharSequence getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, 126 String pkg, int userId) { 127 if (cdm == null) { 128 return ""; 129 } 130 boolean multiple = false; 131 StringBuilder sb = new StringBuilder(); 132 133 try { 134 List<String> associatedMacAddrs = CollectionUtils.mapNotNull( 135 cdm.getAssociations(pkg, userId), 136 a -> a.isSelfManaged() ? null : a.getDeviceMacAddress().toString()); 137 if (associatedMacAddrs != null) { 138 for (String assocMac : associatedMacAddrs) { 139 final Collection<CachedBluetoothDevice> cachedDevices = 140 lbm.getCachedDeviceManager().getCachedDevicesCopy(); 141 for (CachedBluetoothDevice cachedBluetoothDevice : cachedDevices) { 142 if (Objects.equals(assocMac, cachedBluetoothDevice.getAddress())) { 143 if (multiple) { 144 sb.append(", "); 145 } else { 146 multiple = true; 147 } 148 sb.append(cachedBluetoothDevice.getName()); 149 } 150 } 151 } 152 } 153 } catch (RemoteException e) { 154 Log.w(TAG, "Error calling CDM", e); 155 } 156 return sb.toString(); 157 } 158 enableSwitch(Context context, ApplicationInfo app)159 public boolean enableSwitch(Context context, ApplicationInfo app) { 160 try { 161 PackageInfo info = context.getPackageManager().getPackageInfo( 162 app.packageName, PackageManager.GET_PERMISSIONS); 163 final AppRow row = new AppRow(); 164 recordCanBeBlocked(info, row); 165 boolean systemBlockable = !row.systemApp || (row.systemApp && row.banned); 166 return systemBlockable && !row.lockedImportance; 167 } catch (PackageManager.NameNotFoundException e) { 168 e.printStackTrace(); 169 } 170 return false; 171 } 172 getNotificationsBanned(String pkg, int uid)173 public boolean getNotificationsBanned(String pkg, int uid) { 174 try { 175 final boolean enabled = sINM.areNotificationsEnabledForPackage(pkg, uid); 176 return !enabled; 177 } catch (Exception e) { 178 Log.w(TAG, "Error calling NoMan", e); 179 return false; 180 } 181 } 182 setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled)183 public boolean setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { 184 try { 185 if (onlyHasDefaultChannel(pkg, uid)) { 186 NotificationChannel defaultChannel = 187 getChannel(pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, null); 188 defaultChannel.setImportance(enabled ? IMPORTANCE_UNSPECIFIED : IMPORTANCE_NONE); 189 updateChannel(pkg, uid, defaultChannel); 190 } 191 sINM.setNotificationsEnabledForPackage(pkg, uid, enabled); 192 return true; 193 } catch (Exception e) { 194 Log.w(TAG, "Error calling NoMan", e); 195 return false; 196 } 197 } 198 canShowBadge(String pkg, int uid)199 public boolean canShowBadge(String pkg, int uid) { 200 try { 201 return sINM.canShowBadge(pkg, uid); 202 } catch (Exception e) { 203 Log.w(TAG, "Error calling NoMan", e); 204 return false; 205 } 206 } 207 setShowBadge(String pkg, int uid, boolean showBadge)208 public boolean setShowBadge(String pkg, int uid, boolean showBadge) { 209 try { 210 sINM.setShowBadge(pkg, uid, showBadge); 211 return true; 212 } catch (Exception e) { 213 Log.w(TAG, "Error calling NoMan", e); 214 return false; 215 } 216 } 217 getBubblePreference(String pkg, int uid)218 public int getBubblePreference(String pkg, int uid) { 219 try { 220 return sINM.getBubblePreferenceForPackage(pkg, uid); 221 } catch (Exception e) { 222 Log.w(TAG, "Error calling NoMan", e); 223 return -1; 224 } 225 } 226 setAllowBubbles(String pkg, int uid, int preference)227 public boolean setAllowBubbles(String pkg, int uid, int preference) { 228 try { 229 sINM.setBubblesAllowed(pkg, uid, preference); 230 return true; 231 } catch (Exception e) { 232 Log.w(TAG, "Error calling NoMan", e); 233 return false; 234 } 235 } 236 getChannel(String pkg, int uid, String channelId)237 public NotificationChannel getChannel(String pkg, int uid, String channelId) { 238 return getChannel(pkg, uid, channelId, null); 239 } 240 getChannel(String pkg, int uid, String channelId, String conversationId)241 public NotificationChannel getChannel(String pkg, int uid, String channelId, 242 String conversationId) { 243 if (channelId == null) { 244 return null; 245 } 246 try { 247 return sINM.getNotificationChannelForPackage(pkg, uid, channelId, conversationId, true); 248 } catch (Exception e) { 249 Log.w(TAG, "Error calling NoMan", e); 250 return null; 251 } 252 } 253 getGroup(String pkg, int uid, String groupId)254 public NotificationChannelGroup getGroup(String pkg, int uid, String groupId) { 255 if (groupId == null) { 256 return null; 257 } 258 try { 259 return sINM.getNotificationChannelGroupForPackage(groupId, pkg, uid); 260 } catch (Exception e) { 261 Log.w(TAG, "Error calling NoMan", e); 262 return null; 263 } 264 } 265 getGroups(String pkg, int uid)266 public ParceledListSlice<NotificationChannelGroup> getGroups(String pkg, int uid) { 267 try { 268 return sINM.getNotificationChannelGroupsForPackage(pkg, uid, false); 269 } catch (Exception e) { 270 Log.w(TAG, "Error calling NoMan", e); 271 return ParceledListSlice.emptyList(); 272 } 273 } 274 getGroupsWithRecentBlockedFilter(String pkg, int uid)275 public ParceledListSlice<NotificationChannelGroup> getGroupsWithRecentBlockedFilter(String pkg, 276 int uid) { 277 try { 278 return sINM.getRecentBlockedNotificationChannelGroupsForPackage(pkg, uid); 279 } catch (Exception e) { 280 Log.w(TAG, "Error calling NoMan", e); 281 return ParceledListSlice.emptyList(); 282 } 283 } 284 getConversations(String pkg, int uid)285 public ParceledListSlice<ConversationChannelWrapper> getConversations(String pkg, int uid) { 286 try { 287 return sINM.getConversationsForPackage(pkg, uid); 288 } catch (Exception e) { 289 Log.w(TAG, "Error calling NoMan", e); 290 return ParceledListSlice.emptyList(); 291 } 292 } 293 getConversations(boolean onlyImportant)294 public ParceledListSlice<ConversationChannelWrapper> getConversations(boolean onlyImportant) { 295 try { 296 return sINM.getConversations(onlyImportant); 297 } catch (Exception e) { 298 Log.w(TAG, "Error calling NoMan", e); 299 return ParceledListSlice.emptyList(); 300 } 301 } 302 hasSentValidMsg(String pkg, int uid)303 public boolean hasSentValidMsg(String pkg, int uid) { 304 try { 305 return sINM.hasSentValidMsg(pkg, uid); 306 } catch (Exception e) { 307 Log.w(TAG, "Error calling NoMan", e); 308 return false; 309 } 310 } 311 isInInvalidMsgState(String pkg, int uid)312 public boolean isInInvalidMsgState(String pkg, int uid) { 313 try { 314 return sINM.isInInvalidMsgState(pkg, uid); 315 } catch (Exception e) { 316 Log.w(TAG, "Error calling NoMan", e); 317 return false; 318 } 319 } 320 hasUserDemotedInvalidMsgApp(String pkg, int uid)321 public boolean hasUserDemotedInvalidMsgApp(String pkg, int uid) { 322 try { 323 return sINM.hasUserDemotedInvalidMsgApp(pkg, uid); 324 } catch (Exception e) { 325 Log.w(TAG, "Error calling NoMan", e); 326 return false; 327 } 328 } 329 setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted)330 public void setInvalidMsgAppDemoted(String pkg, int uid, boolean isDemoted) { 331 try { 332 sINM.setInvalidMsgAppDemoted(pkg, uid, isDemoted); 333 } catch (Exception e) { 334 Log.w(TAG, "Error calling NoMan", e); 335 } 336 } 337 hasSentValidBubble(String pkg, int uid)338 public boolean hasSentValidBubble(String pkg, int uid) { 339 try { 340 return sINM.hasSentValidBubble(pkg, uid); 341 } catch (Exception e) { 342 Log.w(TAG, "Error calling NoMan", e); 343 return false; 344 } 345 } 346 347 /** 348 * Returns all notification channels associated with the package and uid that will bypass DND 349 */ getNotificationChannelsBypassingDnd(String pkg, int uid)350 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg, 351 int uid) { 352 try { 353 return sINM.getNotificationChannelsBypassingDnd(pkg, uid); 354 } catch (Exception e) { 355 Log.w(TAG, "Error calling NoMan", e); 356 return ParceledListSlice.emptyList(); 357 } 358 } 359 360 /** 361 * Returns all of a user's packages that have at least one channel that will bypass DND 362 */ getPackagesBypassingDnd(int userId, boolean includeConversationChannels)363 public List<String> getPackagesBypassingDnd(int userId, 364 boolean includeConversationChannels) { 365 try { 366 return sINM.getPackagesBypassingDnd(userId, includeConversationChannels); 367 } catch (Exception e) { 368 Log.w(TAG, "Error calling NoMan", e); 369 return new ArrayList<>(); 370 } 371 } 372 updateChannel(String pkg, int uid, NotificationChannel channel)373 public void updateChannel(String pkg, int uid, NotificationChannel channel) { 374 try { 375 sINM.updateNotificationChannelForPackage(pkg, uid, channel); 376 } catch (Exception e) { 377 Log.w(TAG, "Error calling NoMan", e); 378 } 379 } 380 updateChannelGroup(String pkg, int uid, NotificationChannelGroup group)381 public void updateChannelGroup(String pkg, int uid, NotificationChannelGroup group) { 382 try { 383 sINM.updateNotificationChannelGroupForPackage(pkg, uid, group); 384 } catch (Exception e) { 385 Log.w(TAG, "Error calling NoMan", e); 386 } 387 } 388 getDeletedChannelCount(String pkg, int uid)389 public int getDeletedChannelCount(String pkg, int uid) { 390 try { 391 return sINM.getDeletedChannelCount(pkg, uid); 392 } catch (Exception e) { 393 Log.w(TAG, "Error calling NoMan", e); 394 return 0; 395 } 396 } 397 getBlockedChannelCount(String pkg, int uid)398 public int getBlockedChannelCount(String pkg, int uid) { 399 try { 400 return sINM.getBlockedChannelCount(pkg, uid); 401 } catch (Exception e) { 402 Log.w(TAG, "Error calling NoMan", e); 403 return 0; 404 } 405 } 406 onlyHasDefaultChannel(String pkg, int uid)407 public boolean onlyHasDefaultChannel(String pkg, int uid) { 408 try { 409 return sINM.onlyHasDefaultChannel(pkg, uid); 410 } catch (Exception e) { 411 Log.w(TAG, "Error calling NoMan", e); 412 return false; 413 } 414 } 415 getChannelCount(String pkg, int uid)416 public int getChannelCount(String pkg, int uid) { 417 try { 418 return sINM.getNumNotificationChannelsForPackage(pkg, uid, false); 419 } catch (Exception e) { 420 Log.w(TAG, "Error calling NoMan", e); 421 return 0; 422 } 423 } 424 shouldHideSilentStatusBarIcons(Context context)425 public boolean shouldHideSilentStatusBarIcons(Context context) { 426 try { 427 return sINM.shouldHideSilentStatusIcons(context.getPackageName()); 428 } catch (Exception e) { 429 Log.w(TAG, "Error calling NoMan", e); 430 return false; 431 } 432 } 433 setHideSilentStatusIcons(boolean hide)434 public void setHideSilentStatusIcons(boolean hide) { 435 try { 436 sINM.setHideSilentStatusIcons(hide); 437 } catch (Exception e) { 438 Log.w(TAG, "Error calling NoMan", e); 439 } 440 } 441 getAssistantAdjustments(String pkg)442 public List<String> getAssistantAdjustments(String pkg) { 443 try { 444 return sINM.getAllowedAssistantAdjustments(pkg); 445 } catch (Exception e) { 446 Log.w(TAG, "Error calling NoMan", e); 447 } 448 return new ArrayList<>(); 449 } 450 showSilentInStatusBar(String pkg)451 public boolean showSilentInStatusBar(String pkg) { 452 try { 453 return !sINM.shouldHideSilentStatusIcons(pkg); 454 } catch (Exception e) { 455 Log.w(TAG, "Error calling NoMan", e); 456 } 457 return false; 458 } 459 getNotificationHistory(String pkg, String attributionTag)460 public NotificationHistory getNotificationHistory(String pkg, String attributionTag) { 461 try { 462 return sINM.getNotificationHistory(pkg, attributionTag); 463 } catch (Exception e) { 464 Log.w(TAG, "Error calling NoMan", e); 465 } 466 return new NotificationHistory(); 467 } 468 recordAggregatedUsageEvents(Context context, AppRow appRow)469 protected void recordAggregatedUsageEvents(Context context, AppRow appRow) { 470 long now = System.currentTimeMillis(); 471 long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK); 472 UsageEvents events = null; 473 try { 474 events = sUsageStatsManager.queryEventsForPackageForUser( 475 startTime, now, appRow.userId, appRow.pkg, context.getPackageName()); 476 } catch (RemoteException e) { 477 e.printStackTrace(); 478 } 479 recordAggregatedUsageEvents(events, appRow); 480 } 481 recordAggregatedUsageEvents(UsageEvents events, AppRow appRow)482 protected void recordAggregatedUsageEvents(UsageEvents events, AppRow appRow) { 483 appRow.sentByChannel = new HashMap<>(); 484 appRow.sentByApp = new NotificationsSentState(); 485 if (events != null) { 486 UsageEvents.Event event = new UsageEvents.Event(); 487 while (events.hasNextEvent()) { 488 events.getNextEvent(event); 489 490 if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { 491 String channelId = event.mNotificationChannelId; 492 if (channelId != null) { 493 NotificationsSentState stats = appRow.sentByChannel.get(channelId); 494 if (stats == null) { 495 stats = new NotificationsSentState(); 496 appRow.sentByChannel.put(channelId, stats); 497 } 498 if (event.getTimeStamp() > stats.lastSent) { 499 stats.lastSent = event.getTimeStamp(); 500 appRow.sentByApp.lastSent = event.getTimeStamp(); 501 } 502 stats.sentCount++; 503 appRow.sentByApp.sentCount++; 504 calculateAvgSentCounts(stats); 505 } 506 } 507 508 } 509 calculateAvgSentCounts(appRow.sentByApp); 510 } 511 } 512 getSentSummary(Context context, NotificationsSentState state, boolean sortByRecency)513 public static CharSequence getSentSummary(Context context, NotificationsSentState state, 514 boolean sortByRecency) { 515 if (state == null) { 516 return null; 517 } 518 if (sortByRecency) { 519 if (state.lastSent == 0) { 520 return context.getString(R.string.notifications_sent_never); 521 } 522 return StringUtil.formatRelativeTime( 523 context, System.currentTimeMillis() - state.lastSent, true); 524 } else { 525 if (state.avgSentDaily > 0) { 526 return StringUtil.getIcuPluralsString(context, state.avgSentDaily, 527 R.string.notifications_sent_daily); 528 } 529 return StringUtil.getIcuPluralsString(context, state.avgSentWeekly, 530 R.string.notifications_sent_weekly); 531 } 532 } 533 calculateAvgSentCounts(NotificationsSentState stats)534 private void calculateAvgSentCounts(NotificationsSentState stats) { 535 if (stats != null) { 536 stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK); 537 if (stats.sentCount < DAYS_TO_CHECK) { 538 stats.avgSentWeekly = stats.sentCount; 539 } 540 } 541 } 542 getAllowedNotificationAssistant()543 public ComponentName getAllowedNotificationAssistant() { 544 try { 545 return sINM.getAllowedNotificationAssistant(); 546 } catch (Exception e) { 547 Log.w(TAG, "Error calling NoMan", e); 548 return null; 549 } 550 } 551 getDefaultNotificationAssistant()552 public ComponentName getDefaultNotificationAssistant() { 553 try { 554 return sINM.getDefaultNotificationAssistant(); 555 } catch (Exception e) { 556 Log.w(TAG, "Error calling NoMan", e); 557 return null; 558 } 559 } 560 setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig)561 public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) { 562 try { 563 sINM.setNASMigrationDoneAndResetDefault(userId, loadFromConfig); 564 } catch (Exception e) { 565 Log.w(TAG, "Error calling NoMan", e); 566 } 567 } 568 setNotificationAssistantGranted(ComponentName cn)569 public boolean setNotificationAssistantGranted(ComponentName cn) { 570 try { 571 sINM.setNotificationAssistantAccessGranted(cn, true); 572 if (cn == null) { 573 return sINM.getAllowedNotificationAssistant() == null; 574 } else { 575 return cn.equals(sINM.getAllowedNotificationAssistant()); 576 } 577 } catch (Exception e) { 578 Log.w(TAG, "Error calling NoMan", e); 579 return false; 580 } 581 } 582 createConversationNotificationChannel(String pkg, int uid, NotificationChannel parent, String conversationId)583 public void createConversationNotificationChannel(String pkg, int uid, 584 NotificationChannel parent, String conversationId) { 585 try { 586 sINM.createConversationNotificationChannelForPackage(pkg, uid, parent, conversationId); 587 } catch (Exception e) { 588 Log.w(TAG, "Error calling NoMan", e); 589 } 590 } 591 getConversationInfo(Context context, String pkg, int uid, String id)592 public ShortcutInfo getConversationInfo(Context context, String pkg, int uid, String id) { 593 LauncherApps la = context.getSystemService(LauncherApps.class); 594 595 LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() 596 .setPackage(pkg) 597 .setQueryFlags(FLAG_MATCH_DYNAMIC 598 | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER | FLAG_MATCH_CACHED) 599 .setShortcutIds(Arrays.asList(id)); 600 List<ShortcutInfo> shortcuts = la.getShortcuts( 601 query, UserHandle.of(UserHandle.getUserId(uid))); 602 if (shortcuts != null && !shortcuts.isEmpty()) { 603 return shortcuts.get(0); 604 } 605 return null; 606 } 607 getConversationDrawable(Context context, ShortcutInfo info, String pkg, int uid, boolean important)608 public Drawable getConversationDrawable(Context context, ShortcutInfo info, String pkg, 609 int uid, boolean important) { 610 if (info == null) { 611 return null; 612 } 613 ConversationIconFactory iconFactory = new ConversationIconFactory(context, 614 context.getSystemService(LauncherApps.class), 615 context.getPackageManager(), 616 IconDrawableFactory.newInstance(context, false), 617 context.getResources().getDimensionPixelSize( 618 R.dimen.conversation_icon_size)); 619 return iconFactory.getConversationDrawable(info, pkg, uid, important); 620 } 621 requestPinShortcut(Context context, ShortcutInfo shortcutInfo)622 public void requestPinShortcut(Context context, ShortcutInfo shortcutInfo) { 623 ShortcutManager sm = context.getSystemService(ShortcutManager.class); 624 sm.requestPinShortcut(shortcutInfo, null); 625 } 626 resetNotificationImportance()627 public void resetNotificationImportance() { 628 try { 629 sINM.unlockAllNotificationChannels(); 630 } catch (Exception e) { 631 Log.w(TAG, "Error calling NoMan", e); 632 } 633 } 634 getListenerFilter(ComponentName cn, int userId)635 public NotificationListenerFilter getListenerFilter(ComponentName cn, int userId) { 636 NotificationListenerFilter nlf = null; 637 try { 638 nlf = sINM.getListenerFilter(cn, userId); 639 } catch (Exception e) { 640 Log.w(TAG, "Error calling NoMan", e); 641 } 642 return nlf != null ? nlf : new NotificationListenerFilter(); 643 } 644 setListenerFilter(ComponentName cn, int userId, NotificationListenerFilter nlf)645 public void setListenerFilter(ComponentName cn, int userId, NotificationListenerFilter nlf) { 646 try { 647 sINM.setListenerFilter(cn, userId, nlf); 648 } catch (Exception e) { 649 Log.w(TAG, "Error calling NoMan", e); 650 } 651 } 652 isNotificationListenerAccessGranted(ComponentName cn)653 public boolean isNotificationListenerAccessGranted(ComponentName cn) { 654 try { 655 return sINM.isNotificationListenerAccessGranted(cn); 656 } catch (Exception e) { 657 Log.w(TAG, "Error calling NoMan", e); 658 } 659 return false; 660 } 661 662 @VisibleForTesting setNm(INotificationManager inm)663 void setNm(INotificationManager inm) { 664 sINM = inm; 665 } 666 667 /** 668 * NotificationsSentState contains how often an app sends notifications and how recently it sent 669 * one. 670 */ 671 public static class NotificationsSentState { 672 public int avgSentDaily = 0; 673 public int avgSentWeekly = 0; 674 public long lastSent = 0; 675 public int sentCount = 0; 676 } 677 678 static class Row { 679 public String section; 680 } 681 682 public static class AppRow extends Row { 683 public String pkg; 684 public int uid; 685 public Drawable icon; 686 public CharSequence label; 687 public Intent settingsIntent; 688 public boolean banned; 689 public boolean first; // first app in section 690 public boolean systemApp; 691 public boolean lockedImportance; 692 public boolean showBadge; 693 // For apps target T but have not but has not requested the permission 694 // we cannot change the permission state 695 public boolean permissionStateLocked; 696 public int bubblePreference = NotificationManager.BUBBLE_PREFERENCE_NONE; 697 public int userId; 698 public int blockedChannelCount; 699 public int channelCount; 700 public Map<String, NotificationsSentState> sentByChannel; 701 public NotificationsSentState sentByApp; 702 } 703 } 704