OSDN Git Service

Allow companion device mgrs channel access
authorJulia Reynolds <juliacr@google.com>
Tue, 4 Apr 2017 21:04:38 +0000 (17:04 -0400)
committerJulia Reynolds <juliacr@google.com>
Tue, 11 Apr 2017 19:08:59 +0000 (19:08 +0000)
- Callbacks when channels and groups are modified
- Allow them to read and update channels and groups

Test: runtest systemui-notification
Change-Id: Ie4d02bd4583f71f9faf27603bcc59a1ec0eeaf46

12 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/app/INotificationManager.aidl
core/java/android/service/notification/INotificationListener.aidl
core/java/android/service/notification/NotificationAssistantService.java
core/java/android/service/notification/NotificationListenerService.java
services/core/java/com/android/server/notification/ManagedServices.java
services/core/java/com/android/server/notification/NotificationManagerService.java
services/core/java/com/android/server/notification/RankingHelper.java
services/tests/notification/AndroidManifest.xml
services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java

index 77ffa02..dbd3353 100644 (file)
@@ -37306,12 +37306,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<android.app.NotificationChannelGroup> getNotificationChannelGroups(java.lang.String);
+    method public final java.util.List<android.app.NotificationChannel> 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);
@@ -37324,6 +37328,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
@@ -37332,6 +37337,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
index 3f5b571..ea6acca 100644 (file)
@@ -40430,12 +40430,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<android.app.NotificationChannelGroup> getNotificationChannelGroups(java.lang.String);
+    method public final java.util.List<android.app.NotificationChannel> 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);
@@ -40452,6 +40456,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
@@ -40460,6 +40465,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
index 94f9cb2..95fdbd8 100644 (file)
@@ -37491,12 +37491,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<android.app.NotificationChannelGroup> getNotificationChannelGroups(java.lang.String);
+    method public final java.util.List<android.app.NotificationChannel> 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);
@@ -37510,6 +37514,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
@@ -37518,6 +37523,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
index 61dacef..f4e8f3f 100644 (file)
@@ -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<Adjustment> adjustments);
index b26e328..dc1a70d 100644 (file)
@@ -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);
index 6ec9d69..d94017c 100644 (file)
@@ -138,7 +138,6 @@ public abstract class NotificationAssistantService extends NotificationListenerS
         }
     }
 
-
     private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
         @Override
         public void onNotificationEnqueued(IStatusBarNotificationHolder sbnHolder) {
index f55c7cf..4833be3 100644 (file)
 
 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.
  * <p>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:</p>
  * <pre>
  * &lt;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;
@@ -388,6 +426,40 @@ public abstract class NotificationListenerService extends Service {
     }
 
     /**
+     * Implement this method to learn about notification channel modifications.
+     *
+     * <p>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.
+     *
+     * <p>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.
+     *
+     * <p>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.
+     *
+     * <p>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<NotificationChannel> 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.
+     *
+     * <p>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<NotificationChannelGroup> 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;
             }
         }
     }
index 0774779..000cf2e 100644 (file)
@@ -154,6 +154,13 @@ abstract public class ManagedServices {
 
     abstract protected void onServiceAdded(ManagedServiceInfo info);
 
+    protected List<ManagedServiceInfo> getServices() {
+        synchronized (mMutex) {
+            List<ManagedServiceInfo> services = new ArrayList<>(mServices);
+            return services;
+        }
+    }
+
     protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
 
     private ManagedServiceInfo newServiceInfo(IInterface service,
index c1ffce3..4a5f09b 100644 (file)
@@ -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<String> 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<NotificationChannel> 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<NotificationChannel> 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<NotificationChannelGroup>
+                getNotificationChannelGroupsFromPrivilegedListener(
+                INotificationListener token, String pkg) throws RemoteException {
+            ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+            checkHasCompanionDevice(info);
+
+            List<NotificationChannelGroup> 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) {
@@ -4437,11 +4514,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;
         }
@@ -4570,6 +4647,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;
@@ -4656,7 +4744,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) {
@@ -4895,6 +4982,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;
@@ -4949,6 +5088,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;
index b789d5c..9e12f91 100644 (file)
@@ -715,12 +715,12 @@ public class RankingHelper implements RankingConfig {
         return new ParceledListSlice<>(new ArrayList<>(groups.values()));
     }
 
-    public List<String> deleteNotificationChannelGroup(String pkg, int uid,
+    public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
             String groupId) {
-        List<String> deletedChannelIds = new ArrayList<>();
+        List<NotificationChannel> 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
index cf050a8..99d9c7b 100644 (file)
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
index d9214fa..6aa8f3a 100644 (file)
@@ -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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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());
+    }
 }