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