From 1fa865f396e94913cfbbd32ce7799c2e8aeb7a08 Mon Sep 17 00:00:00 2001 From: John Spurlock Date: Mon, 21 Jul 2014 14:56:39 -0400 Subject: [PATCH] New NotificationListenerService listener flags api. Give activated listeners the ability to request changes to listener-level or host-level state. Currently this consists of the ability to suppress notification alerts (alerts = the haptic / audio feedback of a notification). Bug:15888672 Change-Id: I045e3b99d1f15e3f96ebaf17d3083a97e02ecb42 --- api/current.txt | 5 + .../android/app/INotificationManager.aidl | 2 + .../notification/INotificationListener.aidl | 1 + .../NotificationListenerService.java | 64 +++++++++++++ .../NotificationManagerService.java | 95 ++++++++++++++++++- 5 files changed, 165 insertions(+), 2 deletions(-) diff --git a/api/current.txt b/api/current.txt index ce5e36694bda5..cb54a5f9b7049 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27226,14 +27226,19 @@ package android.service.notification { method public final void cancelNotification(java.lang.String); method public final void cancelNotifications(java.lang.String[]); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); + method public final int getCurrentListenerFlags(); method public android.service.notification.NotificationListenerService.RankingMap getCurrentRanking(); method public android.os.IBinder onBind(android.content.Intent); method public void onListenerConnected(); + method public void onListenerFlagsChanged(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); 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 requestListenerFlags(int); + field public static final int FLAG_DISABLE_HOST_ALERTS = 1; // 0x1 + field public static final int FLAG_NONE = 0; // 0x0 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 5b92538fa4131..113c5330bc5aa 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -59,6 +59,8 @@ interface INotificationManager void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token); + void requestFlagsFromListener(in INotificationListener token, int flags); + int getFlagsFromListener(in INotificationListener token); ZenModeConfig getZenModeConfig(); boolean setZenModeConfig(in ZenModeConfig config); diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index b3705d83ab65b..19a8ef594508d 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -28,4 +28,5 @@ oneway interface INotificationListener void onNotificationRemoved(in StatusBarNotification notification, in NotificationRankingUpdate update); void onNotificationRankingUpdate(in NotificationRankingUpdate update); + void onListenerFlagsChanged(int flags); } \ No newline at end of file diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 7d5ff33dcffa0..1fc64e4d3e812 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -53,6 +53,12 @@ public abstract class NotificationListenerService extends Service { private final String TAG = NotificationListenerService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; + /** {@link #getCurrentListenerFlags() Listener flags} constant - default state. **/ + public static final int FLAG_NONE = 0; + /** {@link #getCurrentListenerFlags() Listener flags} constant - the primary device UI + * should disable notification sound, vibrating and other visual or aural effects. **/ + public static final int FLAG_DISABLE_HOST_ALERTS = 1; + private INotificationListenerWrapper mWrapper = null; private RankingMap mRankingMap; @@ -156,6 +162,16 @@ public abstract class NotificationListenerService extends Service { // optional } + /** + * Implement this method to be notified when the + * {@link #getCurrentListenerFlags() listener flags} change. + * + * @param flags The current {@link #getCurrentListenerFlags() listener flags}. + */ + public void onListenerFlagsChanged(int flags) { + // optional + } + private final INotificationManager getNotificationInterface() { if (mNoMan == null) { mNoMan = INotificationManager.Stub.asInterface( @@ -278,6 +294,46 @@ public abstract class NotificationListenerService extends Service { return null; } + /** + * Gets the set of flags representing current state. + * + *

+ * The current state may differ from the requested state if the flag represents state + * shared across all listeners or a feature the notification host does not support or refuses + * to grant. + * + * @return One or more of the FLAG_ constants. + */ + public final int getCurrentListenerFlags() { + if (!isBound()) return FLAG_NONE; + try { + return getNotificationInterface().getFlagsFromListener(mWrapper); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + return FLAG_NONE; + } + } + + /** + * Sets the desired {@link #getCurrentListenerFlags() listener flags}. + * + *

+ * This is merely a request, the host may or not choose to take action depending + * on other listener requests or other global state. + *

+ * Listen for updates using {@link #onListenerFlagsChanged(int)}. + * + * @param flags One or more of the FLAG_ constants. + */ + public final void requestListenerFlags(int flags) { + if (!isBound()) return; + try { + getNotificationInterface().requestFlagsFromListener(mWrapper, flags); + } catch (android.os.RemoteException ex) { + Log.v(TAG, "Unable to contact notification manager", ex); + } + } + /** * Returns current ranking information. * @@ -402,6 +458,14 @@ public abstract class NotificationListenerService extends Service { } } } + @Override + public void onListenerFlagsChanged(int flags) throws RemoteException { + try { + NotificationListenerService.this.onListenerFlagsChanged(flags); + } catch (Throwable t) { + Log.w(TAG, "Error running onListenerFlagsChanged", 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 f69c066f2d4a4..a6711c3023e6d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.service.notification.NotificationListenerService.FLAG_DISABLE_HOST_ALERTS; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -71,6 +72,7 @@ import android.service.notification.ZenModeConfig; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.Log; import android.util.Slog; @@ -121,6 +123,7 @@ public class NotificationManagerService extends SystemService { static final int MESSAGE_RECONSIDER_RANKING = 4; static final int MESSAGE_RANKING_CONFIG_CHANGE = 5; static final int MESSAGE_SEND_RANKING_UPDATE = 6; + static final int MESSAGE_LISTENER_FLAGS_CHANGED = 7; static final int LONG_DELAY = 3500; // 3.5 seconds static final int SHORT_DELAY = 2000; // 2 seconds @@ -169,6 +172,9 @@ public class NotificationManagerService extends SystemService { NotificationRecord mSoundNotification; NotificationRecord mVibrateNotification; + private final ArraySet mListenersDisablingAlerts = new ArraySet<>(); + private int mListenerFlags; // right now, all flags are global + // for enabling and disabling notification pulse behavior private boolean mScreenOn = true; private boolean mInCall = false; @@ -463,7 +469,7 @@ public class NotificationManagerService extends SystemService { public void onSetDisabled(int status) { synchronized (mNotificationList) { mDisableNotificationAlerts = (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0; - if (mDisableNotificationAlerts) { + if (disableNotificationAlerts()) { // cancel whatever's going on long identity = Binder.clearCallingIdentity(); try { @@ -904,6 +910,13 @@ public class NotificationManagerService extends SystemService { } } + private void updateListenerFlagsLocked() { + final int flags = mListenersDisablingAlerts.isEmpty() ? 0 : FLAG_DISABLE_HOST_ALERTS; + if (flags == mListenerFlags) return; + mListenerFlags = flags; + scheduleListenerFlagsChanged(flags); + } + private final IBinder mService = new INotificationManager.Stub() { // Toasts // ============================================================================ @@ -1248,6 +1261,27 @@ public class NotificationManagerService extends SystemService { } } + @Override + public void requestFlagsFromListener(INotificationListener token, int flags) { + synchronized (mNotificationList) { + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + final boolean disableAlerts = (flags & FLAG_DISABLE_HOST_ALERTS) != 0; + if (disableAlerts) { + mListenersDisablingAlerts.add(info); + } else { + mListenersDisablingAlerts.remove(info); + } + updateListenerFlagsLocked(); + } + } + + @Override + public int getFlagsFromListener(INotificationListener token) { + synchronized (mNotificationList) { + return mListenerFlags; + } + } + @Override public ZenModeConfig getZenModeConfig() { enforceSystemOrSystemUI("INotificationManager.getZenModeConfig"); @@ -1339,6 +1373,10 @@ public class NotificationManagerService extends SystemService { return keys.toArray(new String[keys.size()]); } + private boolean disableNotificationAlerts() { + return mDisableNotificationAlerts || (mListenerFlags & FLAG_DISABLE_HOST_ALERTS) != 0; + } + void dumpImpl(PrintWriter pw, DumpFilter filter) { pw.print("Current Notification Manager state"); if (filter != null) { @@ -1422,6 +1460,15 @@ public class NotificationManagerService extends SystemService { pw.println("\n Notification listeners:"); mListeners.dump(pw, filter); + pw.print(" mListenerFlags: "); pw.println(mListenerFlags); + pw.print(" mListenersDisablingAlerts: ("); + N = mListenersDisablingAlerts.size(); + for (int i = 0; i < N; i++) { + final ManagedServiceInfo listener = mListenersDisablingAlerts.valueAt(i); + if (i > 0) pw.print(','); + pw.print(listener.component); + } + pw.println(')'); } pw.println("\n Condition providers:"); @@ -1618,7 +1665,7 @@ public class NotificationManagerService extends SystemService { } // If we're not supposed to beep, vibrate, etc. then don't. - if (!mDisableNotificationAlerts + if (!disableNotificationAlerts() && (!(record.isUpdate && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) && (record.getUserId() == UserHandle.USER_ALL || @@ -1920,6 +1967,17 @@ public class NotificationManagerService extends SystemService { } } + private void scheduleListenerFlagsChanged(int state) { + mHandler.removeMessages(MESSAGE_LISTENER_FLAGS_CHANGED); + mHandler.obtainMessage(MESSAGE_LISTENER_FLAGS_CHANGED, state, 0).sendToTarget(); + } + + private void handleListenerFlagsChanged(int state) { + synchronized (mNotificationList) { + mListeners.notifyListenerFlagsChangedLocked(state); + } + } + private final class WorkerHandler extends Handler { @Override @@ -1936,6 +1994,9 @@ public class NotificationManagerService extends SystemService { case MESSAGE_SEND_RANKING_UPDATE: handleSendRankingUpdate(); break; + case MESSAGE_LISTENER_FLAGS_CHANGED: + handleListenerFlagsChanged(msg.arg1); + break; } } @@ -2445,6 +2506,13 @@ public class NotificationManagerService extends SystemService { } } + @Override + protected void onServiceRemovedLocked(ManagedServiceInfo removed) { + if (mListenersDisablingAlerts.remove(removed)) { + updateListenerFlagsLocked(); + } + } + /** * asynchronously notify all listeners about a new notification */ @@ -2509,6 +2577,20 @@ public class NotificationManagerService extends SystemService { } } + public void notifyListenerFlagsChangedLocked(final int flags) { + for (final ManagedServiceInfo serviceInfo : mServices) { + if (!serviceInfo.isEnabledForCurrentProfiles()) { + continue; + } + mHandler.post(new Runnable() { + @Override + public void run() { + notifyListenerFlagsChanged(serviceInfo, flags); + } + }); + } + } + private void notifyPostedIfUserMatch(final ManagedServiceInfo info, final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { if (!info.enabledAndUserMatches(sbn.getUserId())) { @@ -2544,6 +2626,15 @@ public class NotificationManagerService extends SystemService { Log.e(TAG, "unable to notify listener (ranking update): " + listener, ex); } } + + private void notifyListenerFlagsChanged(ManagedServiceInfo info, int state) { + final INotificationListener listener = (INotificationListener) info.service; + try { + listener.onListenerFlagsChanged(state); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify listener (listener flags): " + listener, ex); + } + } } public static final class DumpFilter {