NoListener: Factor out ZEN mode API

Bug: 17255109
Bug: 17295014
Change-Id: I7e1f6e29b8a23b8e59a8615a8012a86bd5dd22d7
diff --git a/api/current.txt b/api/current.txt
index 6fbb5ce..db3bc66 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27108,9 +27108,11 @@
     method public final void cancelNotifications(java.lang.String[]);
     method public android.service.notification.StatusBarNotification[] getActiveNotifications();
     method public android.service.notification.StatusBarNotification[] getActiveNotifications(java.lang.String[]);
+    method public final int getCurrentInterruptionFilter();
     method public final int getCurrentListenerHints();
     method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking();
     method public android.os.IBinder onBind(android.content.Intent);
+    method public void onInterruptionFilterChanged(int);
     method public void onListenerConnected();
     method public void onListenerHintsChanged(int);
     method public void onNotificationPosted(android.service.notification.StatusBarNotification);
@@ -27118,13 +27120,12 @@
     method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+    method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
-    field public static final int HINTS_NONE = 0; // 0x0
-    field public static final int HINT_HOST_DISABLE_EFFECTS = 4; // 0x4
-    field public static final int HINT_HOST_INTERRUPTION_LEVEL_ALL = 1; // 0x1
-    field public static final int HINT_HOST_INTERRUPTION_LEVEL_NONE = 3; // 0x3
-    field public static final int HINT_HOST_INTERRUPTION_LEVEL_PRIORITY = 2; // 0x2
-    field public static final int HOST_INTERRUPTION_LEVEL_MASK = 3; // 0x3
+    field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
+    field public static final int INTERRUPTION_FILTER_ALL = 1; // 0x1
+    field public static final int INTERRUPTION_FILTER_NONE = 3; // 0x3
+    field public static final int INTERRUPTION_FILTER_PRIORITY = 2; // 0x2
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
   }
 
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 07e9a94..214f50c 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -61,6 +61,8 @@
     ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys);
     void requestHintsFromListener(in INotificationListener token, int hints);
     int getHintsFromListener(in INotificationListener token);
+    void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
+    int getInterruptionFilterFromListener(in INotificationListener token);
 
     ComponentName getEffectsSuppressor();
 
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 93b2d3b..8ca9b6c 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -29,4 +29,5 @@
             in NotificationRankingUpdate update);
     void onNotificationRankingUpdate(in NotificationRankingUpdate update);
     void onListenerHintsChanged(int hints);
-}
\ No newline at end of file
+    void onInterruptionFilterChanged(int interruptionFilter);
+}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 450b9a7..a544b2d 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -58,26 +58,28 @@
     private final String TAG = NotificationListenerService.class.getSimpleName()
             + "[" + getClass().getSimpleName() + "]";
 
-    /** {@link #getCurrentListenerHints() Listener hints} constant - default state. */
-    public static final int HINTS_NONE = 0;
+    /**
+     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
+     *     Normal interruption filter.
+     */
+    public static final int INTERRUPTION_FILTER_ALL = 1;
 
-    /** Bitmask range for {@link #getCurrentListenerHints() Listener hints} host interruption level
-     * constants.  */
-    public static final int HOST_INTERRUPTION_LEVEL_MASK = 0x3;
+    /**
+     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
+     *     Priority interruption filter.
+     */
+    public static final int INTERRUPTION_FILTER_PRIORITY = 2;
 
-    /** {@link #getCurrentListenerHints() Listener hints} constant - Normal interruption level. */
-    public static final int HINT_HOST_INTERRUPTION_LEVEL_ALL = 1;
-
-    /** {@link #getCurrentListenerHints() Listener hints} constant - Priority interruption level. */
-    public static final int HINT_HOST_INTERRUPTION_LEVEL_PRIORITY = 2;
-
-    /** {@link #getCurrentListenerHints() Listener hints} constant - No interruptions level. */
-    public static final int HINT_HOST_INTERRUPTION_LEVEL_NONE = 3;
+    /**
+     * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
+     *     No interruptions filter.
+     */
+    public static final int INTERRUPTION_FILTER_NONE = 3;
 
     /** {@link #getCurrentListenerHints() Listener hints} constant - the primary device UI
      * should disable notification sound, vibrating and other visual or aural effects.
-     * This does not change the interruption level, only the effects. **/
-    public static final int HINT_HOST_DISABLE_EFFECTS = 1 << 2;
+     * This does not change the interruption filter, only the effects. **/
+    public static final int HINT_HOST_DISABLE_EFFECTS = 1;
 
     private INotificationListenerWrapper mWrapper = null;
     private RankingMap mRankingMap;
@@ -197,6 +199,17 @@
         // optional
     }
 
+    /**
+     * Implement this method to be notified when the
+     * {@link #getCurrentInterruptionFilter() interruption filter} changed.
+     *
+     * @param interruptionFilter The current
+     *     {@link #getCurrentInterruptionFilter() interruption filter}.
+     */
+    public void onInterruptionFilterChanged(int interruptionFilter) {
+        // optional
+    }
+
     private final INotificationManager getNotificationInterface() {
         if (mNoMan == null) {
             mNoMan = INotificationManager.Stub.asInterface(
@@ -345,15 +358,42 @@
      * shared across all listeners or a feature the notification host does not support or refuses
      * to grant.
      *
-     * @return One or more of the HINT_ constants.
+     * @return Zero or more of the HINT_ constants.
      */
     public final int getCurrentListenerHints() {
-        if (!isBound()) return HINTS_NONE;
+        if (!isBound()) return 0;
         try {
             return getNotificationInterface().getHintsFromListener(mWrapper);
         } catch (android.os.RemoteException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
-            return HINTS_NONE;
+            return 0;
+        }
+    }
+
+    /**
+     * Gets the current notification interruption filter active on the host.
+     *
+     * <p>
+     * The interruption filter defines which notifications are allowed to interrupt the user
+     * (e.g. via sound &amp; vibration) and is applied globally. Listeners can find out whether
+     * a specific notification matched the interruption filter via
+     * {@link Ranking#matchesInterruptionFilter()}.
+     * <p>
+     * The current filter may differ from the previously requested filter if the notification host
+     * does not support or refuses to apply the requested filter, or if another component changed
+     * the filter in the meantime.
+     * <p>
+     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
+     *
+     * @return One of the INTERRUPTION_FILTER_ constants, or 0 on errors.
+     */
+    public final int getCurrentInterruptionFilter() {
+        if (!isBound()) return 0;
+        try {
+            return getNotificationInterface().getHintsFromListener(mWrapper);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+            return 0;
         }
     }
 
@@ -361,7 +401,7 @@
      * Sets the desired {@link #getCurrentListenerHints() listener hints}.
      *
      * <p>
-     * This is merely a request, the host may or not choose to take action depending
+     * This is merely a request, the host may or may not choose to take action depending
      * on other listener requests or other global state.
      * <p>
      * Listen for updates using {@link #onListenerHintsChanged(int)}.
@@ -378,6 +418,27 @@
     }
 
     /**
+     * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}.
+     *
+     * <p>
+     * This is merely a request, the host may or may not choose to apply the requested
+     * interruption filter depending on other listener requests or other global state.
+     * <p>
+     * Listen for updates using {@link #onInterruptionFilterChanged(int)}.
+     *
+     * @param interruptionFilter One of the INTERRUPTION_FILTER_ constants.
+     */
+    public final void requestInterruptionFilter(int interruptionFilter) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface()
+                    .requestInterruptionFilterFromListener(mWrapper, interruptionFilter);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    /**
      * Returns current ranking information.
      *
      * <p>
@@ -514,6 +575,15 @@
                 Log.w(TAG, "Error running onListenerHintsChanged", t);
             }
         }
+
+        @Override
+        public void onInterruptionFilterChanged(int interruptionFilter) throws RemoteException {
+            try {
+                NotificationListenerService.this.onInterruptionFilterChanged(interruptionFilter);
+            } catch (Throwable t) {
+                Log.w(TAG, "Error running onInterruptionFilterChanged", t);
+            }
+        }
     }
 
     private void applyUpdate(NotificationRankingUpdate update) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8c0d2c9..fc1b746 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -57,7 +57,6 @@
 import android.os.IInterface;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -127,6 +126,7 @@
     static final int MESSAGE_RANKING_CONFIG_CHANGE = 5;
     static final int MESSAGE_SEND_RANKING_UPDATE = 6;
     static final int MESSAGE_LISTENER_HINTS_CHANGED = 7;
+    static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 8;
 
     static final int LONG_DELAY = 3500; // 3.5 seconds
     static final int SHORT_DELAY = 2000; // 2 seconds
@@ -178,6 +178,7 @@
     private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>();
     private ComponentName mEffectsSuppressor;
     private int mListenerHints;  // right now, all hints are global
+    private int mInterruptionFilter;  // current ZEN mode as communicated to listeners
 
     // for enabling and disabling notification pulse behavior
     private boolean mScreenOn = true;
@@ -806,7 +807,7 @@
             @Override
             void onZenModeChanged() {
                 synchronized(mNotificationList) {
-                    updateListenerHintsLocked();
+                    updateInterruptionFilterLocked();
                 }
             }
         });
@@ -938,8 +939,7 @@
     }
 
     private void updateListenerHintsLocked() {
-        final int hints = (mListenersDisablingEffects.isEmpty() ? 0 : HINT_HOST_DISABLE_EFFECTS) |
-                mZenModeHelper.getZenModeListenerHint();
+        final int hints = mListenersDisablingEffects.isEmpty() ? 0 : HINT_HOST_DISABLE_EFFECTS;
         if (hints == mListenerHints) return;
         mListenerHints = hints;
         scheduleListenerHintsChanged(hints);
@@ -954,6 +954,13 @@
                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY));
     }
 
+    private void updateInterruptionFilterLocked() {
+        int interruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
+        if (interruptionFilter == mInterruptionFilter) return;
+        mInterruptionFilter = interruptionFilter;
+        scheduleInterruptionFilterChanged(interruptionFilter);
+    }
+
     private final IBinder mService = new INotificationManager.Stub() {
         // Toasts
         // ============================================================================
@@ -1318,7 +1325,6 @@
                     } else {
                         mListenersDisablingEffects.remove(info);
                     }
-                    mZenModeHelper.requestFromListener(hints);
                     updateListenerHintsLocked();
                     updateEffectsSuppressorLocked();
                 }
@@ -1335,6 +1341,29 @@
         }
 
         @Override
+        public void requestInterruptionFilterFromListener(INotificationListener token,
+                int interruptionFilter) throws RemoteException {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationList) {
+                    mListeners.checkServiceTokenLocked(token);
+                    mZenModeHelper.requestFromListener(interruptionFilter);
+                    updateInterruptionFilterLocked();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public int getInterruptionFilterFromListener(INotificationListener token)
+                throws RemoteException {
+            synchronized (mNotificationLight) {
+                return mInterruptionFilter;
+            }
+        }
+
+        @Override
         public ZenModeConfig getZenModeConfig() {
             enforceSystemOrSystemUI("INotificationManager.getZenModeConfig");
             return mZenModeHelper.getConfig();
@@ -2058,12 +2087,26 @@
         mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget();
     }
 
+    private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) {
+        mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
+        mHandler.obtainMessage(
+                MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED,
+                listenerInterruptionFilter,
+                0).sendToTarget();
+    }
+
     private void handleListenerHintsChanged(int hints) {
         synchronized (mNotificationList) {
             mListeners.notifyListenerHintsChangedLocked(hints);
         }
     }
 
+    private void handleListenerInterruptionFilterChanged(int interruptionFilter) {
+        synchronized (mNotificationList) {
+            mListeners.notifyInterruptionFilterChanged(interruptionFilter);
+        }
+    }
+
     private final class WorkerHandler extends Handler
     {
         @Override
@@ -2083,6 +2126,9 @@
                 case MESSAGE_LISTENER_HINTS_CHANGED:
                     handleListenerHintsChanged(msg.arg1);
                     break;
+                case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED:
+                    handleListenerInterruptionFilterChanged(msg.arg1);
+                    break;
             }
         }
 
@@ -2701,6 +2747,20 @@
             }
         }
 
+        public void notifyInterruptionFilterChanged(final int interruptionFilter) {
+            for (final ManagedServiceInfo serviceInfo : mServices) {
+                if (!serviceInfo.isEnabledForCurrentProfiles()) {
+                    continue;
+                }
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        notifyInterruptionFilterChanged(serviceInfo, interruptionFilter);
+                    }
+                });
+            }
+        }
+
         private void notifyPosted(final ManagedServiceInfo info,
                 final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
             final INotificationListener listener = (INotificationListener)info.service;
@@ -2743,6 +2803,16 @@
             }
         }
 
+        private void notifyInterruptionFilterChanged(ManagedServiceInfo info,
+                int interruptionFilter) {
+            final INotificationListener listener = (INotificationListener) info.service;
+            try {
+                listener.onInterruptionFilterChanged(interruptionFilter);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to notify listener (interruption filter): " + listener, ex);
+            }
+        }
+
         private boolean isListenerPackage(String packageName) {
             if (packageName == null) {
                 return false;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0b93690..7a5336b 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -115,35 +115,35 @@
         mAudioManager = audioManager;
     }
 
-    public int getZenModeListenerHint() {
-        switch(mZenMode) {
+    public int getZenModeListenerInterruptionFilter() {
+        switch (mZenMode) {
             case Global.ZEN_MODE_OFF:
-                return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL;
+                return NotificationListenerService.INTERRUPTION_FILTER_ALL;
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY;
+                return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
             case Global.ZEN_MODE_NO_INTERRUPTIONS:
-                return NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE;
+                return NotificationListenerService.INTERRUPTION_FILTER_NONE;
             default:
                 return 0;
         }
     }
 
-    private static int zenFromListenerHint(int hints, int defValue) {
-        final int level = hints & NotificationListenerService.HOST_INTERRUPTION_LEVEL_MASK;
-        switch(level) {
-            case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_ALL:
+    private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
+            int defValue) {
+        switch (listenerInterruptionFilter) {
+            case NotificationListenerService.INTERRUPTION_FILTER_ALL:
                 return Global.ZEN_MODE_OFF;
-            case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_PRIORITY:
+            case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
                 return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-            case NotificationListenerService.HINT_HOST_INTERRUPTION_LEVEL_NONE:
+            case NotificationListenerService.INTERRUPTION_FILTER_NONE:
                 return Global.ZEN_MODE_NO_INTERRUPTIONS;
             default:
                 return defValue;
         }
     }
 
-    public void requestFromListener(int hints) {
-        final int newZen = zenFromListenerHint(hints, -1);
+    public void requestFromListener(int interruptionFilter) {
+        final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
         if (newZen != -1) {
             setZenMode(newZen, "listener");
         }