diff --git a/api/current.txt b/api/current.txt index fada9aad6c052..7acae2f3e2815 100644 --- a/api/current.txt +++ b/api/current.txt @@ -37304,12 +37304,16 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final java.util.List getNotificationChannelGroups(java.lang.String); + method public final java.util.List getNotificationChannels(java.lang.String); method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); method public void onListenerDisconnected(); method public void onListenerHintsChanged(int); + method public void onNotificationChannelGroupModified(java.lang.String, android.app.NotificationChannelGroup, int); + method public void onNotificationChannelModified(java.lang.String, android.app.NotificationChannel, int); method public void onNotificationPosted(android.service.notification.StatusBarNotification); method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap); method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap); @@ -37322,6 +37326,7 @@ package android.service.notification { method public final void requestUnbind(); method public final void setNotificationsShown(java.lang.String[]); method public final void snoozeNotification(java.lang.String, long); + method public final void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -37330,6 +37335,9 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3 field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2 field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 field public static final int REASON_CANCEL = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index 7f777ee96c837..492342e02ac99 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -40432,12 +40432,16 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final java.util.List getNotificationChannelGroups(java.lang.String); + method public final java.util.List getNotificationChannels(java.lang.String); method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); method public void onListenerDisconnected(); method public void onListenerHintsChanged(int); + method public void onNotificationChannelGroupModified(java.lang.String, android.app.NotificationChannelGroup, int); + method public void onNotificationChannelModified(java.lang.String, android.app.NotificationChannel, int); method public void onNotificationPosted(android.service.notification.StatusBarNotification); method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap); method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap); @@ -40454,6 +40458,7 @@ package android.service.notification { method public final void snoozeNotification(java.lang.String, java.lang.String); method public final void snoozeNotification(java.lang.String, long); method public void unregisterAsSystemService() throws android.os.RemoteException; + method public final void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -40462,6 +40467,9 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3 field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2 field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 field public static final int REASON_CANCEL = 2; // 0x2 diff --git a/api/test-current.txt b/api/test-current.txt index 5c8edac69edbb..cbca13425993d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -37489,12 +37489,16 @@ package android.service.notification { method public final int getCurrentInterruptionFilter(); method public final int getCurrentListenerHints(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); + method public final java.util.List getNotificationChannelGroups(java.lang.String); + method public final java.util.List getNotificationChannels(java.lang.String); method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications(); method public android.os.IBinder onBind(android.content.Intent); method public void onInterruptionFilterChanged(int); method public void onListenerConnected(); method public void onListenerDisconnected(); method public void onListenerHintsChanged(int); + method public void onNotificationChannelGroupModified(java.lang.String, android.app.NotificationChannelGroup, int); + method public void onNotificationChannelModified(java.lang.String, android.app.NotificationChannel, int); method public void onNotificationPosted(android.service.notification.StatusBarNotification); method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap); method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap); @@ -37508,6 +37512,7 @@ package android.service.notification { method public final void setNotificationsShown(java.lang.String[]); method public final void snoozeNotification(java.lang.String, java.lang.String); method public final void snoozeNotification(java.lang.String, long); + method public final void updateNotificationChannel(java.lang.String, android.app.NotificationChannel); field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4 field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2 @@ -37516,6 +37521,9 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3 field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2 field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1; // 0x1 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3; // 0x3 + field public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2; // 0x2 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 field public static final int REASON_CANCEL = 2; // 0x2 diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 61dacefd3f517..f4e8f3f5687e5 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -101,6 +101,10 @@ interface INotificationManager void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim); void setInterruptionFilter(String pkg, int interruptionFilter); + void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in NotificationChannel channel); + ParceledListSlice getNotificationChannelsFromPrivilegedListener(in INotificationListener token, String pkg); + ParceledListSlice getNotificationChannelGroupsFromPrivilegedListener(in INotificationListener token, String pkg); + void applyEnqueuedAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); void applyAdjustmentFromAssistant(in INotificationListener token, in Adjustment adjustment); void applyAdjustmentsFromAssistant(in INotificationListener token, in List adjustments); diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index b26e32835aa1b..dc1a70d74d7fc 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -16,6 +16,8 @@ package android.service.notification; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; import android.service.notification.IStatusBarNotificationHolder; import android.service.notification.StatusBarNotification; import android.service.notification.NotificationRankingUpdate; @@ -33,6 +35,10 @@ oneway interface INotificationListener void onListenerHintsChanged(int hints); void onInterruptionFilterChanged(int interruptionFilter); + // companion device managers only + void onNotificationChannelModification(String pkgName, in NotificationChannel channel, int modificationType); + void onNotificationChannelGroupModification(String pkgName, in NotificationChannelGroup group, int modificationType); + // rankers only void onNotificationEnqueued(in IStatusBarNotificationHolder notificationHolder); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 6ec9d69583e3e..d94017cdb2bf2 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -138,7 +138,6 @@ public abstract class NotificationAssistantService extends NotificationListenerS } } - private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper { @Override public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder) { diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index f55c7cfb4ba54..4833be335c882 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -16,8 +16,13 @@ package android.service.notification; +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.TestApi; import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.companion.CompanionDeviceManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -51,6 +56,8 @@ import android.widget.RemoteViews; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.SomeArgs; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -59,7 +66,7 @@ import java.util.List; * A service that receives calls from the system when new notifications are * posted or removed, or their ranking changed. *

To extend this class, you must declare the service in your manifest file with - * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission + * the {@link Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:

*
  * <service android:name=".NotificationListener"
@@ -215,6 +222,37 @@ public abstract class NotificationListenerService extends Service {
     @SystemApi
     public static final int TRIM_LIGHT = 1;
 
+
+    /** @hide */
+    @IntDef({NOTIFICATION_CHANNEL_OR_GROUP_ADDED, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED,
+            NOTIFICATION_CHANNEL_OR_GROUP_DELETED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ChannelOrGroupModificationTypes {}
+
+    /**
+     * Channel or group modification reason provided to
+     * {@link #onNotificationChannelModified(String, NotificationChannel, int)} or
+     * {@link #onNotificationChannelGroupModified(String, NotificationChannelGroup, int)}- the
+     * provided object was created.
+     */
+    public static final int NOTIFICATION_CHANNEL_OR_GROUP_ADDED = 1;
+
+    /**
+     * Channel or group modification reason provided to
+     * {@link #onNotificationChannelModified(String, NotificationChannel, int)} or
+     * {@link #onNotificationChannelGroupModified(String, NotificationChannelGroup, int)}- the
+     * provided object was updated.
+     */
+    public static final int NOTIFICATION_CHANNEL_OR_GROUP_UPDATED = 2;
+
+    /**
+     * Channel or group modification reason provided to
+     * {@link #onNotificationChannelModified(String, NotificationChannel, int)} or
+     * {@link #onNotificationChannelGroupModified(String, NotificationChannelGroup, int)}- the
+     * provided object was deleted.
+     */
+    public static final int NOTIFICATION_CHANNEL_OR_GROUP_DELETED = 3;
+
     private final Object mLock = new Object();
 
     private Handler mHandler;
@@ -387,6 +425,40 @@ public abstract class NotificationListenerService extends Service {
         // optional
     }
 
+    /**
+     * Implement this method to learn about notification channel modifications.
+     *
+     * 

The caller must have {@link CompanionDeviceManager#getAssociations() an associated + * device} in order to receive this callback. + * + * @param pkg The package the channel belongs to. + * @param channel The channel that has changed. + * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED}, + * {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED}, + * {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}. + */ + public void onNotificationChannelModified(String pkg, NotificationChannel channel, + @ChannelOrGroupModificationTypes int modificationType) { + // optional + } + + /** + * Implement this method to learn about notification channel group modifications. + * + *

The caller must have {@link CompanionDeviceManager#getAssociations() an associated + * device} in order to receive this callback. + * + * @param pkg The package the group belongs to. + * @param group The group that has changed. + * @param modificationType One of {@link #NOTIFICATION_CHANNEL_OR_GROUP_ADDED}, + * {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED}, + * {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}. + */ + public void onNotificationChannelGroupModified(String pkg, NotificationChannelGroup group, + @ChannelOrGroupModificationTypes int modificationType) { + // optional + } + /** * Implement this method to be notified when the * {@link #getCurrentInterruptionFilter() interruption filter} changed. @@ -587,6 +659,69 @@ public abstract class NotificationListenerService extends Service { } } + + /** + * Updates a notification channel for a given package. This should only be used to reflect + * changes a user has made to the channel via the listener's user interface. + * + *

The caller must have {@link CompanionDeviceManager#getAssociations() an associated + * device} in order to use this method. + * + * @param pkg The package the channel belongs to. + * @param channel the channel to update. + */ + public final void updateNotificationChannel(@NonNull String pkg, + @NonNull NotificationChannel channel) { + if (!isBound()) return; + try { + getNotificationInterface().updateNotificationChannelFromPrivilegedListener( + mWrapper, pkg, channel); + } catch (RemoteException e) { + Log.v(TAG, "Unable to contact notification manager", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns all notification channels belonging to the given package. + * + *

The caller must have {@link CompanionDeviceManager#getAssociations() an associated + * device} in order to use this method. + * + * @param pkg The package to retrieve channels for. + */ + public final List getNotificationChannels(@NonNull String pkg) { + if (!isBound()) return null; + try { + + return getNotificationInterface().getNotificationChannelsFromPrivilegedListener( + mWrapper, pkg).getList(); + } catch (RemoteException e) { + Log.v(TAG, "Unable to contact notification manager", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns all notification channel groups belonging to the given package. + * + *

The caller must have {@link CompanionDeviceManager#getAssociations() an associated + * device} in order to use this method. + * + * @param pkg The package to retrieve channel groups for. + */ + public final List getNotificationChannelGroups(@NonNull String pkg) { + if (!isBound()) return null; + try { + + return getNotificationInterface().getNotificationChannelGroupsFromPrivilegedListener( + mWrapper, pkg).getList(); + } catch (RemoteException e) { + Log.v(TAG, "Unable to contact notification manager", e); + throw e.rethrowFromSystemServer(); + } + } + /** * Sets the notification trim that will be received via {@link #onNotificationPosted}. * @@ -1116,6 +1251,28 @@ public abstract class NotificationListenerService extends Service { // no-op in the listener } + @Override + public void onNotificationChannelModification(String pkgName, NotificationChannel channel, + @ChannelOrGroupModificationTypes int modificationType) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = pkgName; + args.arg2 = channel; + args.arg3 = modificationType; + mHandler.obtainMessage( + MyHandler.MSG_ON_NOTIFICATION_CHANNEL_MODIFIED, args).sendToTarget(); + } + + @Override + public void onNotificationChannelGroupModification(String pkgName, + NotificationChannelGroup group, + @ChannelOrGroupModificationTypes int modificationType) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = pkgName; + args.arg2 = group; + args.arg3 = modificationType; + mHandler.obtainMessage( + MyHandler.MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED, args).sendToTarget(); + } } /** @@ -1632,6 +1789,8 @@ public abstract class NotificationListenerService extends Service { public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4; public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5; public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6; + public static final int MSG_ON_NOTIFICATION_CHANNEL_MODIFIED = 7; + public static final int MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED = 8; public MyHandler(Looper looper) { super(looper, null, false); @@ -1678,6 +1837,22 @@ public abstract class NotificationListenerService extends Service { final int interruptionFilter = msg.arg1; onInterruptionFilterChanged(interruptionFilter); } break; + + case MSG_ON_NOTIFICATION_CHANNEL_MODIFIED: { + SomeArgs args = (SomeArgs) msg.obj; + String pkgName = (String) args.arg1; + NotificationChannel channel = (NotificationChannel) args.arg2; + int modificationType = (int) args.arg3; + onNotificationChannelModified(pkgName, channel, modificationType); + } break; + + case MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED: { + SomeArgs args = (SomeArgs) msg.obj; + String pkgName = (String) args.arg1; + NotificationChannelGroup group = (NotificationChannelGroup) args.arg2; + int modificationType = (int) args.arg3; + onNotificationChannelGroupModified(pkgName, group, modificationType); + } break; } } } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0774779a4cf24..000cf2e908c85 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -154,6 +154,13 @@ abstract public class ManagedServices { abstract protected void onServiceAdded(ManagedServiceInfo info); + protected List getServices() { + synchronized (mMutex) { + List services = new ArrayList<>(mServices); + return services; + } + } + protected void onServiceRemovedLocked(ManagedServiceInfo removed) { } private ManagedServiceInfo newServiceInfo(IInterface service, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index d6e82319d1800..3c46e05cb70d2 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -19,6 +19,12 @@ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; +import static android.service.notification.NotificationListenerService + .NOTIFICATION_CHANNEL_OR_GROUP_ADDED; +import static android.service.notification.NotificationListenerService + .NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService + .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; @@ -70,6 +76,7 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; +import android.companion.ICompanionDeviceManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -100,6 +107,7 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; @@ -144,6 +152,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.NotificationVisibility; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -260,6 +269,7 @@ public class NotificationManagerService extends SystemService { Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; private AlarmManager mAlarmManager; + private ICompanionDeviceManager mCompanionManager; final IBinder mForegroundToken = new Binder(); private Handler mHandler; @@ -1004,7 +1014,8 @@ public class NotificationManagerService extends SystemService { // TODO: Tests should call onStart instead once the methods above are removed. @VisibleForTesting void init(Looper looper, IPackageManager packageManager, PackageManager packageManagerClient, - LightsManager lightsManager, NotificationListeners notificationListeners) { + LightsManager lightsManager, NotificationListeners notificationListeners, + ICompanionDeviceManager companionManager) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, @@ -1017,6 +1028,7 @@ public class NotificationManagerService extends SystemService { mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); + mCompanionManager = companionManager; mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1192,7 +1204,8 @@ public class NotificationManagerService extends SystemService { @Override public void onStart() { init(Looper.myLooper(), AppGlobals.getPackageManager(), getContext().getPackageManager(), - getLocalService(LightsManager.class), new NotificationListeners()); + getLocalService(LightsManager.class), new NotificationListeners(), + null); publishBinderService(Context.NOTIFICATION_SERVICE, mService); publishLocalService(NotificationManagerInternal.class, mInternalService); } @@ -1294,7 +1307,8 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); } - private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel) { + private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, + boolean fromListener) { if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { // cancel cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true, @@ -1303,6 +1317,14 @@ public class NotificationManagerService extends SystemService { } mRankingHelper.updateNotificationChannel(pkg, uid, channel); + final NotificationChannel modifiedChannel = + mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false); + + if (!fromListener) { + mListeners.notifyNotificationChannelChanged( + pkg, modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + } + synchronized (mNotificationLock) { final int N = mNotificationList.size(); for (int i = N - 1; i >= 0; --i) { @@ -1311,8 +1333,7 @@ public class NotificationManagerService extends SystemService { && r.sbn.getUid() == uid && channel.getId() != null && channel.getId().equals(r.getChannel().getId())) { - r.updateNotificationChannel(mRankingHelper.getNotificationChannel( - pkg, uid, channel.getId(), false)); + r.updateNotificationChannel(modifiedChannel); } } } @@ -1626,6 +1647,8 @@ public class NotificationManagerService extends SystemService { Preconditions.checkNotNull(group, "group in list is null"); mRankingHelper.createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true /* fromTargetApp */); + mListeners.notifyNotificationChannelGroupChanged(pkg, group, + NOTIFICATION_CHANNEL_OR_GROUP_ADDED); } savePolicyFile(); } @@ -1639,6 +1662,9 @@ public class NotificationManagerService extends SystemService { Preconditions.checkNotNull(channel, "channel in list is null"); mRankingHelper.createNotificationChannel(pkg, uid, channel, true /* fromTargetApp */); + mListeners.notifyNotificationChannelChanged(pkg, + mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false), + NOTIFICATION_CHANNEL_OR_GROUP_ADDED); } savePolicyFile(); } @@ -1674,12 +1700,16 @@ public class NotificationManagerService extends SystemService { @Override public void deleteNotificationChannel(String pkg, String channelId) { checkCallerIsSystemOrSameApp(pkg); + final int callingUid = Binder.getCallingUid(); if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); } cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, - UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null); - mRankingHelper.deleteNotificationChannel(pkg, Binder.getCallingUid(), channelId); + UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null); + mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId); + mListeners.notifyNotificationChannelChanged(pkg, + mRankingHelper.getNotificationChannel(pkg, callingUid, channelId, true), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); savePolicyFile(); } @@ -1692,16 +1722,28 @@ public class NotificationManagerService extends SystemService { } @Override - public void deleteNotificationChannelGroup(String pkg, String channelGroupId) { + public void deleteNotificationChannelGroup(String pkg, String groupId) { checkCallerIsSystemOrSameApp(pkg); - List deletedChannelIds = mRankingHelper.deleteNotificationChannelGroup( - pkg, Binder.getCallingUid(), channelGroupId); - for (int i = 0; i < deletedChannelIds.size(); i++) { - cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannelIds.get(i), 0, 0, true, - UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null); + final int callingUid = Binder.getCallingUid(); + NotificationChannelGroup groupToDelete = + mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid); + if (groupToDelete != null) { + List deletedChannels = + mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId); + for (int i = 0; i < deletedChannels.size(); i++) { + final NotificationChannel deletedChannel = deletedChannels.get(i); + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0, + true, + UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, + null); + mListeners.notifyNotificationChannelChanged(pkg, deletedChannel, + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + } + mListeners.notifyNotificationChannelGroupChanged( + pkg, groupToDelete, NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + savePolicyFile(); } - savePolicyFile(); } @Override @@ -1709,7 +1751,7 @@ public class NotificationManagerService extends SystemService { NotificationChannel channel) { enforceSystemOrSystemUI("Caller not system or systemui"); Preconditions.checkNotNull(channel); - updateNotificationChannelInt(pkg, uid, channel); + updateNotificationChannelInt(pkg, uid, channel, false); } @Override @@ -2646,6 +2688,41 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } } + + @Override + public void updateNotificationChannelFromPrivilegedListener(INotificationListener token, + String pkg, NotificationChannel channel) throws RemoteException { + Preconditions.checkNotNull(channel); + + ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + checkHasCompanionDevice(info); + + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + updateNotificationChannelInt(pkg, uid, channel, true); + } + + @Override + public ParceledListSlice getNotificationChannelsFromPrivilegedListener( + INotificationListener token, String pkg) throws RemoteException { + ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + checkHasCompanionDevice(info); + + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + return mRankingHelper.getNotificationChannels(pkg, uid, false /* includeDeleted */); + } + + @Override + public ParceledListSlice + getNotificationChannelGroupsFromPrivilegedListener( + INotificationListener token, String pkg) throws RemoteException { + ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + checkHasCompanionDevice(info); + + List groups = new ArrayList<>(); + int uid = mPackageManager.getPackageUid(pkg, 0, info.userid); + groups.addAll(mRankingHelper.getNotificationChannelGroups(pkg, uid)); + return new ParceledListSlice<>(groups); + } }; private void applyAdjustment(NotificationRecord n, Adjustment adjustment) { @@ -4445,11 +4522,11 @@ public class NotificationManagerService extends SystemService { return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); } - private boolean isCallerSystem() { + protected boolean isCallerSystem() { return isUidSystem(Binder.getCallingUid()); } - protected void checkCallerIsSystem() { + private void checkCallerIsSystem() { if (isCallerSystem()) { return; } @@ -4578,6 +4655,17 @@ public class NotificationManagerService extends SystemService { channels, overridePeople, snoozeCriteria, showBadge); } + private void checkHasCompanionDevice(ManagedServiceInfo info) throws RemoteException { + if (mCompanionManager == null) { + mCompanionManager = ICompanionDeviceManager.Stub.asInterface( + ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE)); + } + if (ArrayUtils.isEmpty(mCompanionManager.getAssociations( + info.component.getPackageName(), info.userid))) { + throw new SecurityException("Disallowed call from " + info.component); + } + } + private boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) { if (!listener.enabledAndUserMatches(sbn.getUserId())) { return false; @@ -4664,7 +4752,6 @@ public class NotificationManagerService extends SystemService { final StatusBarNotification sbn = r.sbn; TrimCache trimCache = new TrimCache(sbn); - // mServices is the list inside ManagedServices of all the assistants, // There should be only one, but it's a list, so while we enforce // singularity elsewhere, we keep it general here, to avoid surprises. for (final ManagedServiceInfo info : NotificationAssistants.this.mServices) { @@ -4903,6 +4990,58 @@ public class NotificationManagerService extends SystemService { } } + protected void notifyNotificationChannelChanged(final String pkg, + final NotificationChannel channel, final int modificationType) { + if (channel == null) { + return; + } + for (final ManagedServiceInfo serviceInfo : getServices()) { + if (!serviceInfo.isEnabledForCurrentProfiles()) { + continue; + } + try { + checkHasCompanionDevice(serviceInfo); + mHandler.post(new Runnable() { + @Override + public void run() { + notifyNotificationChannelChanged(serviceInfo, pkg, channel, + modificationType); + } + }); + } catch (SecurityException se) { + // Not a privileged listener; do not notify + } catch (RemoteException e) { + Slog.e(TAG, "Cannot reach companion device service", e); + } + } + } + + protected void notifyNotificationChannelGroupChanged(final String pkg, + final NotificationChannelGroup group, final int modificationType) { + if (group == null) { + return; + } + for (final ManagedServiceInfo serviceInfo : getServices()) { + if (!serviceInfo.isEnabledForCurrentProfiles()) { + continue; + } + try { + checkHasCompanionDevice(serviceInfo); + mHandler.post(new Runnable() { + @Override + public void run() { + notifyNotificationChannelGroupChanged(serviceInfo, pkg, group, + modificationType); + } + }); + } catch (SecurityException se) { + // Not a privileged listener; do not notify + } catch (RemoteException e) { + Slog.e(TAG, "Cannot reach companion device service", e); + } + } + } + private void notifyPosted(final ManagedServiceInfo info, final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { final INotificationListener listener = (INotificationListener) info.service; @@ -4957,6 +5096,28 @@ public class NotificationManagerService extends SystemService { } } + void notifyNotificationChannelChanged(ManagedServiceInfo info, + final String pkg, final NotificationChannel channel, + final int modificationType) { + final INotificationListener listener = (INotificationListener) info.service; + try { + listener.onNotificationChannelModification(pkg, channel, modificationType); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify listener (channel changed): " + listener, ex); + } + } + + private void notifyNotificationChannelGroupChanged(ManagedServiceInfo info, + final String pkg, final NotificationChannelGroup group, + final int modificationType) { + final INotificationListener listener = (INotificationListener) info.service; + try { + listener.onNotificationChannelGroupModification(pkg, group, modificationType); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify listener (channel group changed): " + listener, ex); + } + } + public boolean isListenerPackage(String packageName) { if (packageName == null) { return false; diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index b789d5cd602ea..9e12f919c4171 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -715,12 +715,12 @@ public class RankingHelper implements RankingConfig { return new ParceledListSlice<>(new ArrayList<>(groups.values())); } - public List deleteNotificationChannelGroup(String pkg, int uid, + public List deleteNotificationChannelGroup(String pkg, int uid, String groupId) { - List deletedChannelIds = new ArrayList<>(); + List deletedChannels = new ArrayList<>(); Record r = getRecord(pkg, uid); if (r == null || TextUtils.isEmpty(groupId)) { - return deletedChannelIds; + return deletedChannels; } r.groups.remove(groupId); @@ -730,11 +730,11 @@ public class RankingHelper implements RankingConfig { final NotificationChannel nc = r.channels.valueAt(i); if (groupId.equals(nc.getGroup())) { nc.setDeleted(true); - deletedChannelIds.add(nc.getId()); + deletedChannels.add(nc); } } updateConfig(); - return deletedChannelIds; + return deletedChannels; } @Override diff --git a/services/tests/notification/AndroidManifest.xml b/services/tests/notification/AndroidManifest.xml index cf050a89b0e60..99d9c7b873019 100644 --- a/services/tests/notification/AndroidManifest.xml +++ b/services/tests/notification/AndroidManifest.xml @@ -24,6 +24,7 @@ + diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index d9214fae8d168..6aa8f3a8010ef 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import static android.app.NotificationManager.IMPORTANCE_LOW; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; @@ -26,6 +28,8 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,25 +37,29 @@ import static org.mockito.Mockito.when; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.companion.ICompanionDeviceManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.graphics.Color; import android.os.Binder; -import android.os.HandlerThread; -import android.os.MessageQueue; import android.os.UserHandle; +import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.test.annotation.UiThreadTest; import android.support.test.InstrumentationRegistry; import android.testing.TestableLooper; +import java.util.ArrayList; import java.util.Arrays; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.util.List; + +import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -74,17 +82,24 @@ public class NotificationManagerServiceTest { private final RankingHelper mRankingHelper = mock(RankingHelper.class); private NotificationChannel mTestNotificationChannel = new NotificationChannel( TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + private NotificationManagerService.NotificationListeners mNotificationListeners = + mock(NotificationManagerService.NotificationListeners.class); + private ManagedServices.ManagedServiceInfo mListener = + mNotificationListeners.new ManagedServiceInfo( + null, new ComponentName(PKG, "test_class"), uid, true, null, 0); + private ICompanionDeviceManager mCompanionMgr = mock(ICompanionDeviceManager.class); // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { public TestableNotificationManagerService(Context context) { super(context); } @Override - protected void checkCallerIsSystem() {} + protected boolean isCallerSystem() { + return true; + } } @Before - @Test @UiThreadTest public void setUp() throws Exception { mNotificationManagerService = new TestableNotificationManagerService(mContext); @@ -92,7 +107,7 @@ public class NotificationManagerServiceTest { // MockPackageManager - default returns ApplicationInfo with matching calling UID final ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.uid = uid; - when(mPackageManager.getApplicationInfo(any(), anyInt(), anyInt())) + when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt())) .thenReturn(applicationInfo); when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) .thenReturn(applicationInfo); @@ -100,15 +115,10 @@ public class NotificationManagerServiceTest { when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class)); // Use this testable looper. mTestableLooper = new TestableLooper(false); - // Mock NotificationListeners to bypass security checks. - final NotificationManagerService.NotificationListeners mockNotificationListeners = - mock(NotificationManagerService.NotificationListeners.class); - when(mockNotificationListeners.checkServiceTokenLocked(any())).thenReturn( - mockNotificationListeners.new ManagedServiceInfo(null, - new ComponentName(PKG, "test_class"), uid, true, null, 0)); + when(mNotificationListeners.checkServiceTokenLocked(any())).thenReturn(mListener); mNotificationManagerService.init(mTestableLooper.getLooper(), mPackageManager, - mPackageManagerClient, mockLightsManager, mockNotificationListeners); + mPackageManagerClient, mockLightsManager, mNotificationListeners, mCompanionMgr); // Tests call directly into the Binder. mBinderService = mNotificationManagerService.getBinderService(); @@ -419,4 +429,206 @@ public class NotificationManagerServiceTest { verify(mRankingHelper, times(1)).getNotificationChannel( anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean()); } + + @Test + @UiThreadTest + public void testCreateChannelNotifyListener() throws Exception { + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + mNotificationManagerService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + NotificationChannel channel2 = new NotificationChannel("a", "b", IMPORTANCE_LOW); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(channel2.getId()), anyBoolean())) + .thenReturn(channel2); + + reset(mNotificationListeners); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(mTestNotificationChannel, channel2))); + verify(mNotificationListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), + eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED)); + verify(mNotificationListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), + eq(channel2), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED)); + } + + @Test + @UiThreadTest + public void testCreateChannelGroupNotifyListener() throws Exception { + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + mNotificationManagerService.setRankingHelper(mRankingHelper); + NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b"); + NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m"); + + reset(mNotificationListeners); + mBinderService.createNotificationChannelGroups(PKG, + new ParceledListSlice(Arrays.asList(group1, group2))); + verify(mNotificationListeners, times(1)).notifyNotificationChannelGroupChanged(eq(PKG), + eq(group1), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED)); + verify(mNotificationListeners, times(1)).notifyNotificationChannelGroupChanged(eq(PKG), + eq(group2), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED)); + } + + @Test + @UiThreadTest + public void testUpdateChannelNotifyListener() throws Exception { + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + mNotificationManagerService.setRankingHelper(mRankingHelper); + mTestNotificationChannel.setLightColor(Color.CYAN); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + reset(mNotificationListeners); + mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel); + verify(mNotificationListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), + eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test + @UiThreadTest + public void testDeleteChannelNotifyListener() throws Exception { + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + mNotificationManagerService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + reset(mNotificationListeners); + mBinderService.deleteNotificationChannel(PKG, mTestNotificationChannel.getId()); + verify(mNotificationListeners, times(1)).notifyNotificationChannelChanged(eq(PKG), + eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); + } + + @Test + @UiThreadTest + public void testDeleteChannelGroupNotifyListener() throws Exception { + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c"); + mNotificationManagerService.setRankingHelper(mRankingHelper); + when(mRankingHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt())) + .thenReturn(ncg); + reset(mNotificationListeners); + mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId()); + verify(mNotificationListeners, times(1)).notifyNotificationChannelGroupChanged(eq(PKG), + eq(ncg), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); + } + + @Test + @UiThreadTest + public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception { + mNotificationManagerService.setRankingHelper(mRankingHelper); + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + + mBinderService.updateNotificationChannelFromPrivilegedListener( + null, PKG, mTestNotificationChannel); + + verify(mRankingHelper, times(1)).updateNotificationChannel(anyString(), anyInt(), any()); + + verify(mNotificationListeners, never()).notifyNotificationChannelChanged(eq(PKG), + eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test + @UiThreadTest + public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception { + mNotificationManagerService.setRankingHelper(mRankingHelper); + List associations = new ArrayList<>(); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + + try { + mBinderService.updateNotificationChannelFromPrivilegedListener( + null, PKG, mTestNotificationChannel); + fail("listeners that don't have a companion device shouldn't be able to call this"); + } catch (SecurityException e) { + // pass + } + + verify(mRankingHelper, never()).updateNotificationChannel(anyString(), anyInt(), any()); + + verify(mNotificationListeners, never()).notifyNotificationChannelChanged(eq(PKG), + eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test + @UiThreadTest + public void testGetNotificationChannelFromPrivilegedListener_success() throws Exception { + mNotificationManagerService.setRankingHelper(mRankingHelper); + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + + mBinderService.getNotificationChannelsFromPrivilegedListener(null, PKG); + + verify(mRankingHelper, times(1)).getNotificationChannels( + anyString(), anyInt(), anyBoolean()); + } + + @Test + @UiThreadTest + public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception { + mNotificationManagerService.setRankingHelper(mRankingHelper); + List associations = new ArrayList<>(); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + + try { + mBinderService.getNotificationChannelsFromPrivilegedListener(null, PKG); + fail("listeners that don't have a companion device shouldn't be able to call this"); + } catch (SecurityException e) { + // pass + } + + verify(mRankingHelper, never()).getNotificationChannels( + anyString(), anyInt(), anyBoolean()); + } + + @Test + @UiThreadTest + public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception { + mNotificationManagerService.setRankingHelper(mRankingHelper); + List associations = new ArrayList<>(); + associations.add("a"); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + + mBinderService.getNotificationChannelGroupsFromPrivilegedListener(null, PKG); + + verify(mRankingHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt()); + } + + @Test + @UiThreadTest + public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception { + mNotificationManagerService.setRankingHelper(mRankingHelper); + List associations = new ArrayList<>(); + when(mCompanionMgr.getAssociations(PKG, uid)).thenReturn(associations); + + try { + mBinderService.getNotificationChannelGroupsFromPrivilegedListener(null, PKG); + fail("listeners that don't have a companion device shouldn't be able to call this"); + } catch (SecurityException e) { + // pass + } + + verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt()); + } }