• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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