diff --git a/api/current.txt b/api/current.txt index ec00363112126..480ab9ba85bdb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27104,9 +27104,11 @@ package android.service.notification { 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); @@ -27114,13 +27116,12 @@ package android.service.notification { 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 07e9a94f307c8..214f50cbb3d9a 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -61,6 +61,8 @@ interface INotificationManager 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 93b2d3bfadf80..8ca9b6cbd2a10 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -29,4 +29,5 @@ oneway interface INotificationListener 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 450b9a797e6ce..a544b2dbe2534 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -58,26 +58,28 @@ public abstract class NotificationListenerService extends Service { 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 @@ public abstract class NotificationListenerService extends Service { // 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 @@ public abstract class NotificationListenerService extends Service { * 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. + * + *

+ * The interruption filter defines which notifications are allowed to interrupt the user + * (e.g. via sound & vibration) and is applied globally. Listeners can find out whether + * a specific notification matched the interruption filter via + * {@link Ranking#matchesInterruptionFilter()}. + *

+ * 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. + *

+ * 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 @@ public abstract class NotificationListenerService extends Service { * Sets the desired {@link #getCurrentListenerHints() listener hints}. * *

- * 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. *

* Listen for updates using {@link #onListenerHintsChanged(int)}. @@ -377,6 +417,27 @@ public abstract class NotificationListenerService extends Service { } } + /** + * Sets the desired {@link #getCurrentInterruptionFilter() interruption filter}. + * + *

+ * 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. + *

+ * 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. * @@ -514,6 +575,15 @@ public abstract class NotificationListenerService extends Service { 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 8c0d2c91cd20d..fc1b74690be2f 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.IBinder; 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { private final ArraySet 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 @@ public class NotificationManagerService extends SystemService { @Override void onZenModeChanged() { synchronized(mNotificationList) { - updateListenerHintsLocked(); + updateInterruptionFilterLocked(); } } }); @@ -938,8 +939,7 @@ public class NotificationManagerService extends SystemService { } 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 @@ public class NotificationManagerService extends SystemService { .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 @@ public class NotificationManagerService extends SystemService { } else { mListenersDisablingEffects.remove(info); } - mZenModeHelper.requestFromListener(hints); updateListenerHintsLocked(); updateEffectsSuppressorLocked(); } @@ -1334,6 +1340,29 @@ public class NotificationManagerService extends SystemService { } } + @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"); @@ -2058,12 +2087,26 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { case MESSAGE_LISTENER_HINTS_CHANGED: handleListenerHintsChanged(msg.arg1); break; + case MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED: + handleListenerInterruptionFilterChanged(msg.arg1); + break; } } @@ -2701,6 +2747,20 @@ public class NotificationManagerService extends SystemService { } } + 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 @@ public class NotificationManagerService extends SystemService { } } + 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 0b93690ba5f75..7a5336b810a7b 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 @@ public class ZenModeHelper { 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"); }