1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.core.app;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
20 
21 import android.Manifest;
22 import android.app.AppOpsManager;
23 import android.app.Notification;
24 import android.app.NotificationChannel;
25 import android.app.NotificationChannelGroup;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.app.Service;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.ResolveInfo;
36 import android.content.pm.ShortcutInfo;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.os.DeadObjectException;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.os.IBinder;
43 import android.os.Message;
44 import android.os.RemoteException;
45 import android.provider.Settings;
46 import android.service.notification.NotificationListenerService;
47 import android.service.notification.StatusBarNotification;
48 import android.support.v4.app.INotificationSideChannel;
49 import android.util.Log;
50 
51 import androidx.annotation.GuardedBy;
52 import androidx.annotation.IntDef;
53 import androidx.annotation.RequiresApi;
54 import androidx.annotation.RequiresPermission;
55 import androidx.annotation.RestrictTo;
56 import androidx.annotation.VisibleForTesting;
57 
58 import org.jspecify.annotations.NonNull;
59 import org.jspecify.annotations.Nullable;
60 
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 import java.lang.reflect.Field;
64 import java.lang.reflect.InvocationTargetException;
65 import java.lang.reflect.Method;
66 import java.util.ArrayDeque;
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.Collection;
70 import java.util.Collections;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Iterator;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Set;
77 
78 /**
79  * Compatibility library for NotificationManager with fallbacks for older platforms.
80  *
81  * <p>To use this class, call the static function {@link #from} to get a
82  * {@link NotificationManagerCompat} object, and then call one of its
83  * methods to post or cancel notifications.
84  */
85 public final class NotificationManagerCompat {
86     private static final String TAG = "NotifManCompat";
87     private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
88     private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
89 
90     /**
91      * Notification extras key: if set to true, the posted notification should use
92      * the side channel for delivery instead of using notification manager.
93      */
94     public static final String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
95 
96     /**
97      * Intent action to register for on a service to receive side channel
98      * notifications. The listening service must be in the same package as an enabled
99      * {@link NotificationListenerService}.
100      */
101     public static final String ACTION_BIND_SIDE_CHANNEL =
102             "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
103 
104     /**
105      * Maximum sdk build version which needs support for side channeled notifications.
106      * Currently the only needed use is for side channeling group children before KITKAT_WATCH.
107      */
108     static final int MAX_SIDE_CHANNEL_SDK_VERSION = 19;
109 
110     /** Base time delay for a side channel listener queue retry. */
111     private static final int SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS = 1000;
112     /** Maximum retries for a side channel listener before dropping tasks. */
113     private static final int SIDE_CHANNEL_RETRY_MAX_COUNT = 6;
114     /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
115     private static final String SETTING_ENABLED_NOTIFICATION_LISTENERS =
116             "enabled_notification_listeners";
117 
118     /** Cache of enabled notification listener components */
119     private static final Object sEnabledNotificationListenersLock = new Object();
120     @GuardedBy("sEnabledNotificationListenersLock")
121     private static String sEnabledNotificationListeners;
122     @GuardedBy("sEnabledNotificationListenersLock")
123     private static Set<String> sEnabledNotificationListenerPackages = new HashSet<String>();
124 
125     private final Context mContext;
126     private final NotificationManager mNotificationManager;
127     /** Lock for mutable static fields */
128     private static final Object sLock = new Object();
129     @GuardedBy("sLock")
130     private static SideChannelManager sSideChannelManager;
131 
132     @RestrictTo(LIBRARY_GROUP_PREFIX)
133     @IntDef({INTERRUPTION_FILTER_UNKNOWN, INTERRUPTION_FILTER_ALL, INTERRUPTION_FILTER_PRIORITY,
134             INTERRUPTION_FILTER_NONE, INTERRUPTION_FILTER_ALARMS})
135     @Retention(RetentionPolicy.SOURCE)
136     public @interface InterruptionFilter {
137     }
138 
139     /**
140      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
141      *     Normal interruption filter - no notifications are suppressed.
142      */
143     public static final int INTERRUPTION_FILTER_ALL = 1;
144 
145     /**
146      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
147      *     Priority interruption filter - all notifications are suppressed except those that match
148      *     the priority criteria. Some audio streams are muted. See
149      *     {@link Policy#priorityCallSenders}, {@link Policy#priorityCategories},
150      *     {@link Policy#priorityMessageSenders} to define or query this criteria. Users can
151      *     additionally specify packages that can bypass this interruption filter.
152      */
153     public static final int INTERRUPTION_FILTER_PRIORITY = 2;
154 
155     /**
156      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
157      *     No interruptions filter - all notifications are suppressed and all audio streams (except
158      *     those used for phone calls) and vibrations are muted.
159      */
160     public static final int INTERRUPTION_FILTER_NONE = 3;
161 
162     /**
163      * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
164      *     Alarms only interruption filter - all notifications except those of category
165      *     {@link Notification#CATEGORY_ALARM} are suppressed. Some audio streams are muted.
166      */
167     public static final int INTERRUPTION_FILTER_ALARMS = 4;
168 
169     /** {@link #getCurrentInterruptionFilter() Interruption filter} constant -
170      *     returned when the value is unavailable for any reason.
171      */
172     public static final int INTERRUPTION_FILTER_UNKNOWN = 0;
173 
174     /**
175      * Value signifying that the user has not expressed an importance.
176      *
177      * This value is for persisting preferences, and should never be associated with
178      * an actual notification.
179      */
180     public static final int IMPORTANCE_UNSPECIFIED = -1000;
181 
182     /**
183      * A notification with no importance: shows nowhere, is blocked.
184      */
185     public static final int IMPORTANCE_NONE = 0;
186 
187     /**
188      * Min notification importance: only shows in the shade, below the fold.
189      */
190     public static final int IMPORTANCE_MIN = 1;
191 
192     /**
193      * Low notification importance: shows everywhere, but is not intrusive.
194      */
195     public static final int IMPORTANCE_LOW = 2;
196 
197     /**
198      * Default notification importance: shows everywhere, allowed to makes noise,
199      * but does not visually intrude.
200      */
201     public static final int IMPORTANCE_DEFAULT = 3;
202 
203     /**
204      * Higher notification importance: shows everywhere, allowed to makes noise and peek.
205      */
206     public static final int IMPORTANCE_HIGH = 4;
207 
208     /**
209      * Highest notification importance: shows everywhere, allowed to makes noise, peek, and
210      * use full screen intents.
211      */
212     public static final int IMPORTANCE_MAX = 5;
213 
214     /** Get a {@link NotificationManagerCompat} instance for a provided context. */
from(@onNull Context context)215     public static @NonNull NotificationManagerCompat from(@NonNull Context context) {
216         return new NotificationManagerCompat(context);
217     }
218 
NotificationManagerCompat(Context context)219     private NotificationManagerCompat(Context context) {
220         mContext = context;
221         mNotificationManager = (NotificationManager) mContext.getSystemService(
222                 Context.NOTIFICATION_SERVICE);
223     }
224 
225     @VisibleForTesting
NotificationManagerCompat(@onNull NotificationManager notificationManager, @NonNull Context context)226     NotificationManagerCompat(@NonNull NotificationManager notificationManager,
227             @NonNull Context context) {
228         mContext = context;
229         mNotificationManager = notificationManager;
230     }
231 
232     /**
233      * Cancel a previously shown notification.
234      *
235      * @param id the ID of the notification
236      */
cancel(int id)237     public void cancel(int id) {
238         cancel(null, id);
239     }
240 
241     /**
242      * Cancel a previously shown notification.
243      *
244      * @param tag the string identifier of the notification.
245      * @param id  the ID of the notification
246      */
cancel(@ullable String tag, int id)247     public void cancel(@Nullable String tag, int id) {
248         mNotificationManager.cancel(tag, id);
249         if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
250             pushSideChannelQueue(new CancelTask(mContext.getPackageName(), id, tag));
251         }
252     }
253 
254     /** Cancel all previously shown notifications. */
cancelAll()255     public void cancelAll() {
256         mNotificationManager.cancelAll();
257         if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
258             pushSideChannelQueue(new CancelTask(mContext.getPackageName()));
259         }
260     }
261 
262     /**
263      * Post a notification to be shown in the status bar, stream, etc.
264      *
265      * @param id           the ID of the notification
266      * @param notification the notification to post to the system
267      */
268     @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
notify(int id, @NonNull Notification notification)269     public void notify(int id, @NonNull Notification notification) {
270         notify(null, id, notification);
271     }
272 
273     /**
274      * Post a notification to be shown in the status bar, stream, etc.
275      *
276      * @param tag          the string identifier for a notification. Can be {@code null}.
277      * @param id           the ID of the notification. The pair (tag, id) must be unique within
278      *                     your app.
279      * @param notification the notification to post to the system
280      */
281     @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
notify(@ullable String tag, int id, @NonNull Notification notification)282     public void notify(@Nullable String tag, int id, @NonNull Notification notification) {
283         if (useSideChannelForNotification(notification)) {
284             pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification));
285             // Cancel this notification in notification manager if it just transitioned to being
286             // side channelled.
287             mNotificationManager.cancel(tag, id);
288         } else {
289             mNotificationManager.notify(tag, id, notification);
290         }
291     }
292 
293     /**
294      * Post a number of notifications, to be shown in the status bar, stream, etc.
295      * Each notification will attempt to be posted in the order provided in the {@code
296      * notificationWithIds} list. Each notification must have a provided id and may have a
297      * provided tag.
298      *
299      * This is the preferred method for posting groups of notifications, to improve sound and
300      * animation behavior.
301      */
302     @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
notify(@onNull List<NotificationWithIdAndTag> notificationWithIdAndTags)303     public void notify(@NonNull List<NotificationWithIdAndTag> notificationWithIdAndTags) {
304         final int notificationsSize = notificationWithIdAndTags.size();
305         for (int i = 0; i < notificationsSize; i++) {
306             NotificationWithIdAndTag notificationWithIdAndTag = notificationWithIdAndTags.get(i);
307             notify(notificationWithIdAndTag.mTag, notificationWithIdAndTag.mId,
308                     notificationWithIdAndTag.mNotification);
309         }
310     }
311 
312     /**
313      * Helper class which encapsulates a Notification, its id, and optionally a tag, for use when
314      * batch-posting a number of notifications.
315      */
316     public static class NotificationWithIdAndTag {
317         final String mTag;
318         final int mId;
319         Notification mNotification;
320 
NotificationWithIdAndTag(@ullable String tag, int id, @NonNull Notification notification)321         public NotificationWithIdAndTag(@Nullable String tag, int id,
322                 @NonNull Notification notification) {
323             this.mTag = tag;
324             this.mId = id;
325             this.mNotification = notification;
326         }
327 
NotificationWithIdAndTag(int id, @NonNull Notification notification)328         public NotificationWithIdAndTag(int id, @NonNull Notification notification) {
329             this(null, id, notification);
330         }
331     }
332 
333     /**
334      * Recover a list of active notifications: ones that have been posted by the calling app that
335      * have not yet been dismissed by the user or {@link #cancel(String, int)}ed by the app.
336      *
337      * <p><Each notification is embedded in a {@link StatusBarNotification} object, including the
338      * original <code>tag</code> and <code>id</code> supplied to
339      * {@link #notify(String, int, Notification) notify()}
340      * (via {@link StatusBarNotification#getTag() getTag()} and
341      * {@link StatusBarNotification#getId() getId()}) as well as a copy of the original
342      * {@link Notification} object (via {@link StatusBarNotification#getNotification()}).
343      * </p>
344      * <p>From {@link Build.VERSION_CODES#Q}, will also return notifications you've posted as an
345      * app's notification delegate via
346      * {@link NotificationManager#notifyAsPackage(String, String, int, Notification)}.
347      * </p>
348      * <p>
349      *     Returns an empty list on {@link Build.VERSION_CODES#LOLLIPOP_MR1} and earlier.
350      * </p>
351      *
352      * @return A list of {@link StatusBarNotification}.
353      */
getActiveNotifications()354     public @NonNull List<StatusBarNotification> getActiveNotifications() {
355         if (Build.VERSION.SDK_INT >= 23) {
356             return Api23Impl.getActiveNotifications(mNotificationManager);
357         } else {
358             return new ArrayList<>();
359         }
360     }
361 
362     /**
363      * Returns whether notifications from the calling package are not blocked.
364      */
areNotificationsEnabled()365     public boolean areNotificationsEnabled() {
366         if (Build.VERSION.SDK_INT >= 24) {
367             return Api24Impl.areNotificationsEnabled(mNotificationManager);
368         } else {
369             AppOpsManager appOps =
370                     (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
371             ApplicationInfo appInfo = mContext.getApplicationInfo();
372             String pkg = mContext.getApplicationContext().getPackageName();
373             int uid = appInfo.uid;
374             try {
375                 Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());
376                 Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE,
377                         Integer.TYPE, String.class);
378                 Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
379                 int value = (int) opPostNotificationValue.get(Integer.class);
380                 return ((int) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg)
381                         == AppOpsManager.MODE_ALLOWED);
382             } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException
383                     | InvocationTargetException | IllegalAccessException | RuntimeException e) {
384                 return true;
385             }
386         }
387     }
388 
389     /**
390      * Returns the user specified importance for notifications from the calling package.
391      *
392      * @return An importance level, such as {@link #IMPORTANCE_DEFAULT}.
393      */
getImportance()394     public int getImportance() {
395         if (Build.VERSION.SDK_INT >= 24) {
396             return Api24Impl.getImportance(mNotificationManager);
397         } else {
398             return IMPORTANCE_UNSPECIFIED;
399         }
400     }
401 
402     /**
403      * Creates a notification channel that notifications can be posted to.
404      *
405      * This can also be used to restore a deleted channel and to update an existing channel's
406      * name, description, group, and/or importance.
407      *
408      * <p>The importance of an existing channel will only be changed if the new importance is lower
409      * than the current value and the user has not altered any settings on this channel.
410      *
411      * <p>The group of an existing channel will only be changed if the channel does not already
412      * belong to a group.
413      *
414      * All other fields are ignored for channels that already exist.
415      *
416      * It doesn't do anything on older SDKs which don't support Notification Channels.
417      *
418      * @param channel the channel to create.  Note that the created channel may differ from this
419      *                value. If the provided channel is malformed, a RemoteException will be
420      *                thrown.
421      */
createNotificationChannel(@onNull NotificationChannel channel)422     public void createNotificationChannel(@NonNull NotificationChannel channel) {
423         if (Build.VERSION.SDK_INT >= 26) {
424             Api26Impl.createNotificationChannel(mNotificationManager, channel);
425         }
426     }
427 
428     /**
429      * Creates a notification channel that notifications can be posted to.
430      *
431      * This can also be used to restore a deleted channel and to update an existing channel's
432      * name, description, group, and/or importance.
433      *
434      * <p>The importance of an existing channel will only be changed if the new importance is lower
435      * than the current value and the user has not altered any settings on this channel.
436      *
437      * <p>The group of an existing channel will only be changed if the channel does not already
438      * belong to a group.
439      *
440      * All other fields are ignored for channels that already exist.
441      *
442      * It doesn't do anything on older SDKs which don't support Notification Channels.
443      *
444      * @param channel the channel to create.  Note that the created channel may differ from this
445      *                value. If the provided channel is malformed, a RemoteException will be
446      *                thrown.
447      */
createNotificationChannel(@onNull NotificationChannelCompat channel)448     public void createNotificationChannel(@NonNull NotificationChannelCompat channel) {
449         createNotificationChannel(channel.getNotificationChannel());
450     }
451 
452     /**
453      * Creates a group container for {@link NotificationChannel} objects.
454      *
455      * This can be used to rename an existing group.
456      *
457      * It doesn't do anything on older SDKs which don't support Notification Channels.
458      *
459      * @param group The group to create
460      */
createNotificationChannelGroup(@onNull NotificationChannelGroup group)461     public void createNotificationChannelGroup(@NonNull NotificationChannelGroup group) {
462         if (Build.VERSION.SDK_INT >= 26) {
463             Api26Impl.createNotificationChannelGroup(mNotificationManager, group);
464         }
465     }
466 
467     /**
468      * Creates a group container for {@link NotificationChannel} objects.
469      *
470      * This can be used to rename an existing group.
471      *
472      * It doesn't do anything on older SDKs which don't support Notification Channels.
473      *
474      * @param group The group to create
475      */
createNotificationChannelGroup(@onNull NotificationChannelGroupCompat group)476     public void createNotificationChannelGroup(@NonNull NotificationChannelGroupCompat group) {
477         createNotificationChannelGroup(group.getNotificationChannelGroup());
478     }
479 
480     /**
481      * Creates multiple notification channels that different notifications can be posted to. See
482      * {@link #createNotificationChannel(NotificationChannel)}.
483      *
484      * It doesn't do anything on older SDKs which don't support Notification Channels.
485      *
486      * @param channels the list of channels to attempt to create.
487      */
createNotificationChannels(@onNull List<NotificationChannel> channels)488     public void createNotificationChannels(@NonNull List<NotificationChannel> channels) {
489         if (Build.VERSION.SDK_INT >= 26) {
490             Api26Impl.createNotificationChannels(mNotificationManager, channels);
491         }
492     }
493 
494     /**
495      * Creates multiple notification channels that different notifications can be posted to. See
496      * {@link #createNotificationChannel(NotificationChannelCompat)}.
497      *
498      * It doesn't do anything on older SDKs which don't support Notification Channels.
499      *
500      * @param channels the list of channels to attempt to create.
501      */
createNotificationChannelsCompat( @onNull List<NotificationChannelCompat> channels)502     public void createNotificationChannelsCompat(
503             @NonNull List<NotificationChannelCompat> channels) {
504         if (Build.VERSION.SDK_INT >= 26 && !channels.isEmpty()) {
505             List<NotificationChannel> platformChannels = new ArrayList<>(channels.size());
506             for (NotificationChannelCompat channel : channels) {
507                 platformChannels.add(channel.getNotificationChannel());
508             }
509             Api26Impl.createNotificationChannels(mNotificationManager, platformChannels);
510         }
511     }
512 
513     /**
514      * Creates multiple notification channel groups. See
515      * {@link #createNotificationChannelGroup(NotificationChannelGroup)}.
516      *
517      * It doesn't do anything on older SDKs which don't support Notification Channels.
518      *
519      * @param groups The list of groups to create
520      */
createNotificationChannelGroups(@onNull List<NotificationChannelGroup> groups)521     public void createNotificationChannelGroups(@NonNull List<NotificationChannelGroup> groups) {
522         if (Build.VERSION.SDK_INT >= 26) {
523             Api26Impl.createNotificationChannelGroups(mNotificationManager, groups);
524         }
525     }
526 
527     /**
528      * Creates multiple notification channel groups. See
529      * {@link #createNotificationChannelGroup(NotificationChannelGroupCompat)}.
530      *
531      * It doesn't do anything on older SDKs which don't support Notification Channels.
532      *
533      * @param groups The list of groups to create
534      */
createNotificationChannelGroupsCompat( @onNull List<NotificationChannelGroupCompat> groups)535     public void createNotificationChannelGroupsCompat(
536             @NonNull List<NotificationChannelGroupCompat> groups) {
537         if (Build.VERSION.SDK_INT >= 26 && !groups.isEmpty()) {
538             List<NotificationChannelGroup> platformGroups = new ArrayList<>(groups.size());
539             for (NotificationChannelGroupCompat group : groups) {
540                 platformGroups.add(group.getNotificationChannelGroup());
541             }
542             Api26Impl.createNotificationChannelGroups(mNotificationManager, platformGroups);
543         }
544     }
545 
546     /**
547      * Deletes the given notification channel.
548      *
549      * <p>If you {@link #createNotificationChannel(NotificationChannel) create} a new channel with
550      * this same id, the deleted channel will be un-deleted with all of the same settings it
551      * had before it was deleted.
552      *
553      * It doesn't do anything on older SDKs which don't support Notification Channels.
554      */
deleteNotificationChannel(@onNull String channelId)555     public void deleteNotificationChannel(@NonNull String channelId) {
556         if (Build.VERSION.SDK_INT >= 26) {
557             Api26Impl.deleteNotificationChannel(mNotificationManager, channelId);
558         }
559     }
560 
561     /**
562      * Deletes the given notification channel group, and all notification channels that
563      * belong to it.
564      *
565      * It doesn't do anything on older SDKs which don't support Notification Channels.
566      */
deleteNotificationChannelGroup(@onNull String groupId)567     public void deleteNotificationChannelGroup(@NonNull String groupId) {
568         if (Build.VERSION.SDK_INT >= 26) {
569             Api26Impl.deleteNotificationChannelGroup(mNotificationManager, groupId);
570         }
571     }
572 
573     /**
574      * Deletes notification channels for which ids are NOT given.
575      *
576      * This will NOT delete channels which are conversation children of the given channels.
577      *
578      * It doesn't do anything on older SDKs which don't support Notification Channels.
579      *
580      * @param channelIds the IDs of any channels which should NOT be deleted by this method.
581      */
deleteUnlistedNotificationChannels(@onNull Collection<String> channelIds)582     public void deleteUnlistedNotificationChannels(@NonNull Collection<String> channelIds) {
583         if (Build.VERSION.SDK_INT >= 26) {
584             for (NotificationChannel channel :
585                     Api26Impl.getNotificationChannels(mNotificationManager)) {
586                 if (channelIds.contains(Api26Impl.getId(channel))) {
587                     continue;
588                 }
589                 if (Build.VERSION.SDK_INT >= 30
590                         && channelIds.contains(Api30Impl.getParentChannelId(channel))) {
591                     continue;
592                 }
593                 Api26Impl.deleteNotificationChannel(mNotificationManager,
594                         Api26Impl.getId(channel));
595             }
596         }
597     }
598 
599     /**
600      * Returns the notification channel settings for a given channel id.
601      *
602      * Returns {@code null} on older SDKs which don't support Notification Channels.
603      */
getNotificationChannel(@onNull String channelId)604     public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId) {
605         if (Build.VERSION.SDK_INT >= 26) {
606             return Api26Impl.getNotificationChannel(mNotificationManager, channelId);
607         }
608         return null;
609     }
610 
611     /**
612      * Returns the notification channel settings for a given channel id.
613      *
614      * Returns {@code null} on older SDKs which don't support Notification Channels.
615      */
getNotificationChannelCompat( @onNull String channelId)616     public @Nullable NotificationChannelCompat getNotificationChannelCompat(
617             @NonNull String channelId) {
618         if (Build.VERSION.SDK_INT >= 26) {
619             NotificationChannel channel = getNotificationChannel(channelId);
620             if (channel != null) {
621                 return new NotificationChannelCompat(channel);
622             }
623         }
624         return null;
625     }
626 
627     /**
628      * Returns the notification channel settings for a given channel and
629      * {@link ShortcutInfo#getId() conversation id}.
630      *
631      * Returns the channel for the channelId on older SDKs which don't support Conversations.
632      *
633      * Returns {@code null} on older SDKs which don't support Notification Channels.
634      */
getNotificationChannel(@onNull String channelId, @NonNull String conversationId)635     public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
636             @NonNull String conversationId) {
637         if (Build.VERSION.SDK_INT >= 30) {
638             return Api30Impl.getNotificationChannel(mNotificationManager, channelId,
639                     conversationId);
640         }
641         return getNotificationChannel(channelId);
642     }
643 
644     /**
645      * Returns the notification channel settings for a given channel and
646      * {@link ShortcutInfo#getId() conversation id}.
647      *
648      * Returns the channel for the channelId on older SDKs which don't support Conversations.
649      *
650      * Returns {@code null} on older SDKs which don't support Notification Channels.
651      */
getNotificationChannelCompat( @onNull String channelId, @NonNull String conversationId)652     public @Nullable NotificationChannelCompat getNotificationChannelCompat(
653             @NonNull String channelId, @NonNull String conversationId) {
654         if (Build.VERSION.SDK_INT >= 26) {
655             NotificationChannel channel = getNotificationChannel(channelId, conversationId);
656             if (channel != null) {
657                 return new NotificationChannelCompat(channel);
658             }
659         }
660         return null;
661     }
662 
663     /**
664      * Returns the notification channel group settings for a given channel group id.
665      *
666      * Returns {@code null} on older SDKs which don't support Notification Channels.
667      */
getNotificationChannelGroup( @onNull String channelGroupId)668     public @Nullable NotificationChannelGroup getNotificationChannelGroup(
669             @NonNull String channelGroupId) {
670         if (Build.VERSION.SDK_INT >= 28) {
671             return Api28Impl.getNotificationChannelGroup(mNotificationManager, channelGroupId);
672         } else if (Build.VERSION.SDK_INT >= 26) {
673             // find the group in list by its ID
674             for (NotificationChannelGroup group : getNotificationChannelGroups()) {
675                 if (Api26Impl.getId(group).equals(channelGroupId)) return group;
676             }
677             // requested group doesn't exist
678             return null;
679         } else {
680             return null;
681         }
682     }
683 
684     /**
685      * Returns the notification channel group settings for a given channel group id.
686      *
687      * Returns {@code null} on older SDKs which don't support Notification Channels.
688      */
getNotificationChannelGroupCompat( @onNull String channelGroupId)689     public @Nullable NotificationChannelGroupCompat getNotificationChannelGroupCompat(
690             @NonNull String channelGroupId) {
691         if (Build.VERSION.SDK_INT >= 28) {
692             NotificationChannelGroup group = getNotificationChannelGroup(channelGroupId);
693             if (group != null) {
694                 return new NotificationChannelGroupCompat(group);
695             }
696         } else if (Build.VERSION.SDK_INT >= 26) {
697             NotificationChannelGroup group = getNotificationChannelGroup(channelGroupId);
698             if (group != null) {
699                 return new NotificationChannelGroupCompat(group, getNotificationChannels());
700             }
701         }
702         return null;
703     }
704 
705     /**
706      * Returns all notification channels belonging to the calling app
707      * or an empty list on older SDKs which don't support Notification Channels.
708      */
getNotificationChannels()709     public @NonNull List<NotificationChannel> getNotificationChannels() {
710         if (Build.VERSION.SDK_INT >= 26) {
711             return Api26Impl.getNotificationChannels(mNotificationManager);
712         }
713         return Collections.emptyList();
714     }
715 
716     /**
717      * Returns all notification channels belonging to the calling app
718      * or an empty list on older SDKs which don't support Notification Channels.
719      */
720     @SuppressWarnings("MixedMutabilityReturnType")
getNotificationChannelsCompat()721     public @NonNull List<NotificationChannelCompat> getNotificationChannelsCompat() {
722         if (Build.VERSION.SDK_INT >= 26) {
723             List<NotificationChannel> channels = getNotificationChannels();
724             if (!channels.isEmpty()) {
725                 List<NotificationChannelCompat> channelsCompat = new ArrayList<>(channels.size());
726                 for (NotificationChannel channel : channels) {
727                     channelsCompat.add(new NotificationChannelCompat(channel));
728                 }
729                 return channelsCompat;
730             }
731         }
732         return Collections.emptyList();
733     }
734 
735     /**
736      * Returns all notification channel groups belonging to the calling app
737      * or an empty list on older SDKs which don't support Notification Channels.
738      */
getNotificationChannelGroups()739     public @NonNull List<NotificationChannelGroup> getNotificationChannelGroups() {
740         if (Build.VERSION.SDK_INT >= 26) {
741             return Api26Impl.getNotificationChannelGroups(mNotificationManager);
742         }
743         return Collections.emptyList();
744     }
745 
746     /**
747      * Returns all notification channel groups belonging to the calling app
748      * or an empty list on older SDKs which don't support Notification Channels.
749      */
750     @SuppressWarnings("MixedMutabilityReturnType")
getNotificationChannelGroupsCompat()751     public @NonNull List<NotificationChannelGroupCompat> getNotificationChannelGroupsCompat() {
752         if (Build.VERSION.SDK_INT >= 26) {
753             List<NotificationChannelGroup> groups = getNotificationChannelGroups();
754             if (!groups.isEmpty()) {
755                 // Don't query getNotificationChannels() on API 28+ where it isn't needed
756                 List<NotificationChannel> allChannels = Build.VERSION.SDK_INT >= 28
757                         ? Collections.<NotificationChannel>emptyList()
758                         : getNotificationChannels();
759                 List<NotificationChannelGroupCompat> groupsCompat = new ArrayList<>(groups.size());
760                 for (NotificationChannelGroup group : groups) {
761                     if (Build.VERSION.SDK_INT >= 28) {
762                         groupsCompat.add(new NotificationChannelGroupCompat(group));
763                     } else {
764                         groupsCompat.add(new NotificationChannelGroupCompat(group, allChannels));
765                     }
766                 }
767                 return groupsCompat;
768             }
769         }
770         return Collections.emptyList();
771     }
772 
773     /**
774      * Get the set of packages that have an enabled notification listener component within them.
775      */
getEnabledListenerPackages(@onNull Context context)776     public static @NonNull Set<String> getEnabledListenerPackages(@NonNull Context context) {
777         final String enabledNotificationListeners = Settings.Secure.getString(
778                 context.getContentResolver(),
779                 SETTING_ENABLED_NOTIFICATION_LISTENERS);
780         synchronized (sEnabledNotificationListenersLock) {
781             // Parse the string again if it is different from the last time this method was called.
782             if (enabledNotificationListeners != null
783                     && !enabledNotificationListeners.equals(sEnabledNotificationListeners)) {
784                 final String[] components = enabledNotificationListeners.split(":", -1);
785                 Set<String> packageNames = new HashSet<String>(components.length);
786                 for (String component : components) {
787                     ComponentName componentName = ComponentName.unflattenFromString(component);
788                     if (componentName != null) {
789                         packageNames.add(componentName.getPackageName());
790                     }
791                 }
792                 sEnabledNotificationListenerPackages = packageNames;
793                 sEnabledNotificationListeners = enabledNotificationListeners;
794             }
795             return sEnabledNotificationListenerPackages;
796         }
797     }
798 
799     /**
800      * Returns whether the calling app can send fullscreen intents.
801      *
802      * <p>Fullscreen intents were introduced in Android
803      * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, where apps could always attach a full
804      * screen intent to their notification via
805      * {@link Notification.Builder#setFullScreenIntent(PendingIntent, boolean)}}.
806      *
807      * <p>Android {@link android.os.Build.VERSION_CODES#Q} introduced the
808      * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}
809      * permission, where SystemUI will only show the full screen intent attached to a notification
810      * if the permission is declared in the manifest.
811      *
812      * <p>Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, apps
813      * may not have permission to use {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}. If
814      * the FSI permission is denied, SystemUI will show the notification as an expanded heads up
815      * notification on lockscreen.
816      *
817      * <p>To request access, add the {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}
818      * permission to your manifest, and use
819      * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT} to send the user
820      * to the settings page where they can grant your app the FSI permission.
821      */
canUseFullScreenIntent()822     public boolean canUseFullScreenIntent() {
823         if (Build.VERSION.SDK_INT < 29) {
824             return true;
825         }
826         if (Build.VERSION.SDK_INT < 34) {
827             final int permissionState =
828                     mContext.checkSelfPermission(Manifest.permission.USE_FULL_SCREEN_INTENT);
829             return permissionState == PackageManager.PERMISSION_GRANTED;
830         }
831         return Api34Impl.canUseFullScreenIntent(mNotificationManager);
832     }
833 
834     /**
835      * Returns true if this notification should use the side channel for delivery.
836      */
useSideChannelForNotification(Notification notification)837     private static boolean useSideChannelForNotification(Notification notification) {
838         Bundle extras = NotificationCompat.getExtras(notification);
839         return extras != null && extras.getBoolean(EXTRA_USE_SIDE_CHANNEL);
840     }
841 
842     /**
843      * Gets the current notification interruption filter.
844      * <p>
845      * The interruption filter defines which notifications are allowed to
846      * interrupt the user (e.g. via sound &amp; vibration) and is applied
847      * globally.
848      */
getCurrentInterruptionFilter()849     public @InterruptionFilter int getCurrentInterruptionFilter() {
850         if (Build.VERSION.SDK_INT < 23) {
851             // Prior to API 23, Interruption Filters were not implemented, so we return
852             // unknown filter level.
853             return INTERRUPTION_FILTER_UNKNOWN;
854         }
855         return Api23Impl.getCurrentInterruptionFilter(mNotificationManager);
856     }
857 
858     /**
859      * Push a notification task for distribution to notification side channels.
860      */
pushSideChannelQueue(Task task)861     private void pushSideChannelQueue(Task task) {
862         synchronized (sLock) {
863             if (sSideChannelManager == null) {
864                 sSideChannelManager = new SideChannelManager(mContext.getApplicationContext());
865             }
866             sSideChannelManager.queueTask(task);
867         }
868     }
869 
870     /**
871      * Helper class to manage a queue of pending tasks to send to notification side channel
872      * listeners.
873      */
874     private static class SideChannelManager implements Handler.Callback, ServiceConnection {
875         private static final int MSG_QUEUE_TASK = 0;
876         private static final int MSG_SERVICE_CONNECTED = 1;
877         private static final int MSG_SERVICE_DISCONNECTED = 2;
878         private static final int MSG_RETRY_LISTENER_QUEUE = 3;
879 
880         private final Context mContext;
881         private final HandlerThread mHandlerThread;
882         private final Handler mHandler;
883         private final Map<ComponentName, ListenerRecord> mRecordMap =
884                 new HashMap<ComponentName, ListenerRecord>();
885         private Set<String> mCachedEnabledPackages = new HashSet<String>();
886 
SideChannelManager(Context context)887         SideChannelManager(Context context) {
888             mContext = context;
889             mHandlerThread = new HandlerThread("NotificationManagerCompat");
890             mHandlerThread.start();
891             mHandler = new Handler(mHandlerThread.getLooper(), this);
892         }
893 
894         /**
895          * Queue a new task to be sent to all listeners. This function can be called
896          * from any thread.
897          */
queueTask(Task task)898         public void queueTask(Task task) {
899             mHandler.obtainMessage(MSG_QUEUE_TASK, task).sendToTarget();
900         }
901 
902         @Override
handleMessage(Message msg)903         public boolean handleMessage(Message msg) {
904             switch (msg.what) {
905                 case MSG_QUEUE_TASK:
906                     handleQueueTask((Task) msg.obj);
907                     return true;
908                 case MSG_SERVICE_CONNECTED:
909                     ServiceConnectedEvent event = (ServiceConnectedEvent) msg.obj;
910                     handleServiceConnected(event.componentName, event.iBinder);
911                     return true;
912                 case MSG_SERVICE_DISCONNECTED:
913                     handleServiceDisconnected((ComponentName) msg.obj);
914                     return true;
915                 case MSG_RETRY_LISTENER_QUEUE:
916                     handleRetryListenerQueue((ComponentName) msg.obj);
917                     return true;
918             }
919             return false;
920         }
921 
handleQueueTask(Task task)922         private void handleQueueTask(Task task) {
923             updateListenerMap();
924             for (ListenerRecord record : mRecordMap.values()) {
925                 record.taskQueue.add(task);
926                 processListenerQueue(record);
927             }
928         }
929 
handleServiceConnected(ComponentName componentName, IBinder iBinder)930         private void handleServiceConnected(ComponentName componentName, IBinder iBinder) {
931             ListenerRecord record = mRecordMap.get(componentName);
932             if (record != null) {
933                 record.service = INotificationSideChannel.Stub.asInterface(iBinder);
934                 record.retryCount = 0;
935                 processListenerQueue(record);
936             }
937         }
938 
handleServiceDisconnected(ComponentName componentName)939         private void handleServiceDisconnected(ComponentName componentName) {
940             ListenerRecord record = mRecordMap.get(componentName);
941             if (record != null) {
942                 ensureServiceUnbound(record);
943             }
944         }
945 
handleRetryListenerQueue(ComponentName componentName)946         private void handleRetryListenerQueue(ComponentName componentName) {
947             ListenerRecord record = mRecordMap.get(componentName);
948             if (record != null) {
949                 processListenerQueue(record);
950             }
951         }
952 
953         @Override
onServiceConnected(ComponentName componentName, IBinder iBinder)954         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
955             if (Log.isLoggable(TAG, Log.DEBUG)) {
956                 Log.d(TAG, "Connected to service " + componentName);
957             }
958             mHandler.obtainMessage(MSG_SERVICE_CONNECTED,
959                     new ServiceConnectedEvent(componentName, iBinder))
960                     .sendToTarget();
961         }
962 
963         @Override
onServiceDisconnected(ComponentName componentName)964         public void onServiceDisconnected(ComponentName componentName) {
965             if (Log.isLoggable(TAG, Log.DEBUG)) {
966                 Log.d(TAG, "Disconnected from service " + componentName);
967             }
968             mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, componentName).sendToTarget();
969         }
970 
971         /**
972          * Check the current list of enabled listener packages and update the records map
973          * accordingly.
974          */
975         @SuppressWarnings("deprecation")
updateListenerMap()976         private void updateListenerMap() {
977             Set<String> enabledPackages = getEnabledListenerPackages(mContext);
978             if (enabledPackages.equals(mCachedEnabledPackages)) {
979                 // Short-circuit when the list of enabled packages has not changed.
980                 return;
981             }
982             mCachedEnabledPackages = enabledPackages;
983             List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices(
984                     new Intent().setAction(ACTION_BIND_SIDE_CHANNEL), 0);
985             Set<ComponentName> enabledComponents = new HashSet<ComponentName>();
986             for (ResolveInfo resolveInfo : resolveInfos) {
987                 if (!enabledPackages.contains(resolveInfo.serviceInfo.packageName)) {
988                     continue;
989                 }
990                 ComponentName componentName = new ComponentName(
991                         resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
992                 if (resolveInfo.serviceInfo.permission != null) {
993                     Log.w(TAG, "Permission present on component " + componentName
994                             + ", not adding listener record.");
995                     continue;
996                 }
997                 enabledComponents.add(componentName);
998             }
999             // Ensure all enabled components have a record in the listener map.
1000             for (ComponentName componentName : enabledComponents) {
1001                 if (!mRecordMap.containsKey(componentName)) {
1002                     if (Log.isLoggable(TAG, Log.DEBUG)) {
1003                         Log.d(TAG, "Adding listener record for " + componentName);
1004                     }
1005                     mRecordMap.put(componentName, new ListenerRecord(componentName));
1006                 }
1007             }
1008             // Remove listener records that are no longer for enabled components.
1009             Iterator<Map.Entry<ComponentName, ListenerRecord>> it =
1010                     mRecordMap.entrySet().iterator();
1011             while (it.hasNext()) {
1012                 Map.Entry<ComponentName, ListenerRecord> entry = it.next();
1013                 if (!enabledComponents.contains(entry.getKey())) {
1014                     if (Log.isLoggable(TAG, Log.DEBUG)) {
1015                         Log.d(TAG, "Removing listener record for " + entry.getKey());
1016                     }
1017                     ensureServiceUnbound(entry.getValue());
1018                     it.remove();
1019                 }
1020             }
1021         }
1022 
1023         /**
1024          * Ensure we are already attempting to bind to a service, or start a new binding if not.
1025          *
1026          * @return Whether the service bind attempt was successful.
1027          */
ensureServiceBound(ListenerRecord record)1028         private boolean ensureServiceBound(ListenerRecord record) {
1029             if (record.bound) {
1030                 return true;
1031             }
1032             Intent intent = new Intent(ACTION_BIND_SIDE_CHANNEL).setComponent(record.componentName);
1033             record.bound = mContext.bindService(intent, this, Service.BIND_AUTO_CREATE
1034                     | Service.BIND_WAIVE_PRIORITY);
1035             if (record.bound) {
1036                 record.retryCount = 0;
1037             } else {
1038                 Log.w(TAG, "Unable to bind to listener " + record.componentName);
1039                 mContext.unbindService(this);
1040             }
1041             return record.bound;
1042         }
1043 
1044         /**
1045          * Ensure we have unbound from a service.
1046          */
ensureServiceUnbound(ListenerRecord record)1047         private void ensureServiceUnbound(ListenerRecord record) {
1048             if (record.bound) {
1049                 mContext.unbindService(this);
1050                 record.bound = false;
1051             }
1052             record.service = null;
1053         }
1054 
1055         /**
1056          * Schedule a delayed retry to communicate with a listener service.
1057          * After a maximum number of attempts (with exponential back-off), start
1058          * dropping pending tasks for this listener.
1059          */
scheduleListenerRetry(ListenerRecord record)1060         private void scheduleListenerRetry(ListenerRecord record) {
1061             if (mHandler.hasMessages(MSG_RETRY_LISTENER_QUEUE, record.componentName)) {
1062                 return;
1063             }
1064             record.retryCount++;
1065             if (record.retryCount > SIDE_CHANNEL_RETRY_MAX_COUNT) {
1066                 Log.w(TAG, "Giving up on delivering " + record.taskQueue.size() + " tasks to "
1067                         + record.componentName + " after " + record.retryCount + " retries");
1068                 record.taskQueue.clear();
1069                 return;
1070             }
1071             int delayMs = SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS * (1 << (record.retryCount - 1));
1072             if (Log.isLoggable(TAG, Log.DEBUG)) {
1073                 Log.d(TAG, "Scheduling retry for " + delayMs + " ms");
1074             }
1075             Message msg = mHandler.obtainMessage(MSG_RETRY_LISTENER_QUEUE, record.componentName);
1076             mHandler.sendMessageDelayed(msg, delayMs);
1077         }
1078 
1079         /**
1080          * Perform a processing step for a listener. First check the bind state, then attempt
1081          * to flush the task queue, and if an error is encountered, schedule a retry.
1082          */
processListenerQueue(ListenerRecord record)1083         private void processListenerQueue(ListenerRecord record) {
1084             if (Log.isLoggable(TAG, Log.DEBUG)) {
1085                 Log.d(TAG, "Processing component " + record.componentName + ", "
1086                         + record.taskQueue.size() + " queued tasks");
1087             }
1088             if (record.taskQueue.isEmpty()) {
1089                 return;
1090             }
1091             if (!ensureServiceBound(record) || record.service == null) {
1092                 // Ensure bind has started and that a service interface is ready to use.
1093                 scheduleListenerRetry(record);
1094                 return;
1095             }
1096             // Attempt to flush all items in the task queue.
1097             while (true) {
1098                 Task task = record.taskQueue.peek();
1099                 if (task == null) {
1100                     break;
1101                 }
1102                 try {
1103                     if (Log.isLoggable(TAG, Log.DEBUG)) {
1104                         Log.d(TAG, "Sending task " + task);
1105                     }
1106                     task.send(record.service);
1107                     record.taskQueue.remove();
1108                 } catch (DeadObjectException e) {
1109                     if (Log.isLoggable(TAG, Log.DEBUG)) {
1110                         Log.d(TAG, "Remote service has died: " + record.componentName);
1111                     }
1112                     break;
1113                 } catch (RemoteException e) {
1114                     Log.w(TAG, "RemoteException communicating with " + record.componentName, e);
1115                     break;
1116                 }
1117             }
1118             if (!record.taskQueue.isEmpty()) {
1119                 // Some tasks were not sent, meaning an error was encountered, schedule a retry.
1120                 scheduleListenerRetry(record);
1121             }
1122         }
1123 
1124         /** A per-side-channel-service listener state record */
1125         private static class ListenerRecord {
1126             final ComponentName componentName;
1127             /** Whether the service is currently bound to. */
1128             boolean bound = false;
1129             /** The service stub provided by onServiceConnected */
1130             INotificationSideChannel service;
1131             /** Queue of pending tasks to send to this listener service */
1132             ArrayDeque<Task> taskQueue = new ArrayDeque<>();
1133             /** Number of retries attempted while connecting to this listener service */
1134             int retryCount = 0;
1135 
ListenerRecord(ComponentName componentName)1136             ListenerRecord(ComponentName componentName) {
1137                 this.componentName = componentName;
1138             }
1139         }
1140     }
1141 
1142     private static class ServiceConnectedEvent {
1143         final ComponentName componentName;
1144         final IBinder iBinder;
1145 
ServiceConnectedEvent(ComponentName componentName, final IBinder iBinder)1146         ServiceConnectedEvent(ComponentName componentName,
1147                 final IBinder iBinder) {
1148             this.componentName = componentName;
1149             this.iBinder = iBinder;
1150         }
1151     }
1152 
1153     private interface Task {
send(INotificationSideChannel service)1154         void send(INotificationSideChannel service) throws RemoteException;
1155     }
1156 
1157     private static class NotifyTask implements Task {
1158         final String packageName;
1159         final int id;
1160         final String tag;
1161         final Notification notif;
1162 
NotifyTask(String packageName, int id, String tag, Notification notif)1163         NotifyTask(String packageName, int id, String tag, Notification notif) {
1164             this.packageName = packageName;
1165             this.id = id;
1166             this.tag = tag;
1167             this.notif = notif;
1168         }
1169 
1170         @Override
send(INotificationSideChannel service)1171         public void send(INotificationSideChannel service) throws RemoteException {
1172             service.notify(packageName, id, tag, notif);
1173         }
1174 
1175         @Override
toString()1176         public @NonNull String toString() {
1177             StringBuilder sb = new StringBuilder("NotifyTask[");
1178             sb.append("packageName:").append(packageName);
1179             sb.append(", id:").append(id);
1180             sb.append(", tag:").append(tag);
1181             sb.append("]");
1182             return sb.toString();
1183         }
1184     }
1185 
1186     private static class CancelTask implements Task {
1187         final String packageName;
1188         final int id;
1189         final String tag;
1190         final boolean all;
1191 
CancelTask(String packageName)1192         CancelTask(String packageName) {
1193             this.packageName = packageName;
1194             this.id = 0;
1195             this.tag = null;
1196             this.all = true;
1197         }
1198 
CancelTask(String packageName, int id, String tag)1199         CancelTask(String packageName, int id, String tag) {
1200             this.packageName = packageName;
1201             this.id = id;
1202             this.tag = tag;
1203             this.all = false;
1204         }
1205 
1206         @Override
send(INotificationSideChannel service)1207         public void send(INotificationSideChannel service) throws RemoteException {
1208             if (all) {
1209                 service.cancelAll(packageName);
1210             } else {
1211                 service.cancel(packageName, id, tag);
1212             }
1213         }
1214 
1215         @Override
toString()1216         public @NonNull String toString() {
1217             StringBuilder sb = new StringBuilder("CancelTask[");
1218             sb.append("packageName:").append(packageName);
1219             sb.append(", id:").append(id);
1220             sb.append(", tag:").append(tag);
1221             sb.append(", all:").append(all);
1222             sb.append("]");
1223             return sb.toString();
1224         }
1225     }
1226 
1227     /**
1228      * A class for wrapping calls to {@link NotificationManager} methods which
1229      * were added in API 23; these calls must be wrapped to avoid performance issues.
1230      * See the UnsafeNewApiCall lint rule for more details.
1231      */
1232     @RequiresApi(23)
1233     static class Api23Impl {
Api23Impl()1234         private Api23Impl() { }
1235 
getActiveNotifications( NotificationManager notificationManager)1236         static List<StatusBarNotification> getActiveNotifications(
1237                 NotificationManager notificationManager) {
1238             StatusBarNotification[] notifs = notificationManager.getActiveNotifications();
1239             if (notifs == null) {
1240                 return new ArrayList<>();
1241             }
1242             return Arrays.asList(notifs);
1243         }
1244 
getCurrentInterruptionFilter( NotificationManager notificationManager)1245         static int getCurrentInterruptionFilter(
1246                 NotificationManager notificationManager) {
1247             return notificationManager.getCurrentInterruptionFilter();
1248         }
1249     }
1250 
1251     /**
1252      * A class for wrapping calls to {@link NotificationManager} methods which
1253      * were added in API 24; these calls must be wrapped to avoid performance issues.
1254      * See the UnsafeNewApiCall lint rule for more details.
1255      */
1256     @RequiresApi(24)
1257     static class Api24Impl {
Api24Impl()1258         private Api24Impl() { }
1259 
areNotificationsEnabled(NotificationManager notificationManager)1260         static boolean areNotificationsEnabled(NotificationManager notificationManager) {
1261             return notificationManager.areNotificationsEnabled();
1262         }
1263 
getImportance(NotificationManager notificationManager)1264         static int getImportance(NotificationManager notificationManager) {
1265             return notificationManager.getImportance();
1266         }
1267     }
1268 
1269     /**
1270      * A class for wrapping calls to {@link Notification.Builder} methods which
1271      * were added in API 26; these calls must be wrapped to avoid performance issues.
1272      * See the UnsafeNewApiCall lint rule for more details.
1273      */
1274     @RequiresApi(26)
1275     static class Api26Impl {
Api26Impl()1276         private Api26Impl() {
1277             // This class is not instantiable.
1278         }
1279 
createNotificationChannel(NotificationManager notificationManager, NotificationChannel channel)1280         static void createNotificationChannel(NotificationManager notificationManager,
1281                 NotificationChannel channel) {
1282             notificationManager.createNotificationChannel(channel);
1283         }
1284 
getNotificationChannel(NotificationManager notificationManager, String channelId)1285         static NotificationChannel getNotificationChannel(NotificationManager notificationManager,
1286                 String channelId) {
1287             return notificationManager.getNotificationChannel(channelId);
1288         }
1289 
createNotificationChannels( NotificationManager notificationManager, List<NotificationChannel> channels)1290         static void createNotificationChannels(
1291                 NotificationManager notificationManager, List<NotificationChannel> channels) {
1292             notificationManager.createNotificationChannels(channels);
1293         }
1294 
getNotificationChannels( NotificationManager notificationManager)1295         static List<NotificationChannel> getNotificationChannels(
1296                 NotificationManager notificationManager) {
1297             return notificationManager.getNotificationChannels();
1298         }
1299 
createNotificationChannelGroup(NotificationManager notificationManager, NotificationChannelGroup group)1300         static void createNotificationChannelGroup(NotificationManager notificationManager,
1301                 NotificationChannelGroup group) {
1302             notificationManager.createNotificationChannelGroup(group);
1303         }
1304 
createNotificationChannelGroups(NotificationManager notificationManager, List<NotificationChannelGroup> groups)1305         static void createNotificationChannelGroups(NotificationManager notificationManager,
1306                 List<NotificationChannelGroup> groups) {
1307             notificationManager.createNotificationChannelGroups(groups);
1308         }
1309 
getNotificationChannelGroups( NotificationManager notificationManager)1310         static List<NotificationChannelGroup> getNotificationChannelGroups(
1311                 NotificationManager notificationManager) {
1312             return notificationManager.getNotificationChannelGroups();
1313         }
1314 
deleteNotificationChannel(NotificationManager notificationManager, String channelId)1315         static void deleteNotificationChannel(NotificationManager notificationManager,
1316                 String channelId) {
1317             notificationManager.deleteNotificationChannel(channelId);
1318         }
1319 
deleteNotificationChannelGroup(NotificationManager notificationManager, String groupId)1320         static void deleteNotificationChannelGroup(NotificationManager notificationManager,
1321                 String groupId) {
1322             notificationManager.deleteNotificationChannelGroup(groupId);
1323         }
1324 
1325 
getId(NotificationChannel notificationChannel)1326         static String getId(NotificationChannel notificationChannel) {
1327             return notificationChannel.getId();
1328         }
1329 
getId(NotificationChannelGroup notificationChannelGroup)1330         static String getId(NotificationChannelGroup notificationChannelGroup) {
1331             return notificationChannelGroup.getId();
1332         }
1333     }
1334 
1335     /**
1336      * A class for wrapping calls to {@link Notification.Builder} methods which
1337      * were added in API 28; these calls must be wrapped to avoid performance issues.
1338      * See the UnsafeNewApiCall lint rule for more details.
1339      */
1340     @RequiresApi(28)
1341     static class Api28Impl {
Api28Impl()1342         private Api28Impl() { }
1343 
getNotificationChannelGroup( NotificationManager notificationManager, String channelGroupId)1344         static NotificationChannelGroup getNotificationChannelGroup(
1345                 NotificationManager notificationManager, String channelGroupId) {
1346             return notificationManager.getNotificationChannelGroup(channelGroupId);
1347         }
1348     }
1349 
1350     /**
1351      * A class for wrapping calls to {@link Notification.Builder} methods which
1352      * were added in API 30; these calls must be wrapped to avoid performance issues.
1353      * See the UnsafeNewApiCall lint rule for more details.
1354      */
1355     @RequiresApi(30)
1356     static class Api30Impl {
Api30Impl()1357         private Api30Impl() { }
1358 
getParentChannelId(NotificationChannel notificationChannel)1359         static String getParentChannelId(NotificationChannel notificationChannel) {
1360             return notificationChannel.getParentChannelId();
1361         }
1362 
getNotificationChannel(NotificationManager notificationManager, String channelId, String conversationId)1363         static NotificationChannel getNotificationChannel(NotificationManager notificationManager,
1364                 String channelId, String conversationId) {
1365             return notificationManager.getNotificationChannel(channelId, conversationId);
1366         }
1367     }
1368 
1369     /**
1370      * A class for wrapping calls to {@link Notification.Builder} methods which
1371      * were added in API 34; these calls must be wrapped to avoid performance issues.
1372      * See the UnsafeNewApiCall lint rule for more details.
1373      */
1374     @RequiresApi(34)
1375     static class Api34Impl {
Api34Impl()1376         private Api34Impl() { }
1377 
canUseFullScreenIntent(NotificationManager notificationManager)1378         static boolean canUseFullScreenIntent(NotificationManager notificationManager) {
1379             return notificationManager.canUseFullScreenIntent();
1380         }
1381     }
1382 }
1383