From ab41eecf22352f54167ce9a272a397715ffd0015 Mon Sep 17 00:00:00 2001 From: Chris Wren Date: Mon, 4 Jan 2016 18:01:27 -0500 Subject: [PATCH] allow listeners to disable themselves This decouples the user permisson grant from the bound state of the listener. This allows listeners that are only relevant sometimes to avoid being bound as a foreground service all the time. Bug: 19232554 Change-Id: I9ab078630af5a10c6878d3f19ba80661299713d5 --- api/current.txt | 2 + api/system-current.txt | 2 + api/test-current.txt | 2 + .../android/app/INotificationManager.aidl | 3 + .../NotificationAssistantService.java | 5 ++ .../NotificationListenerService.java | 30 +++++++++ .../server/notification/ManagedServices.java | 65 ++++++++++++++++++- .../NotificationManagerService.java | 32 +++++++++ 8 files changed, 138 insertions(+), 3 deletions(-) diff --git a/api/current.txt b/api/current.txt index 30d302093272b..743ce07e97633 100644 --- a/api/current.txt +++ b/api/current.txt @@ -33549,6 +33549,8 @@ package android.service.notification { 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); + method public static final void requestRebind(android.content.ComponentName) throws android.os.RemoteException; + method public final void requestUnbind() throws android.os.RemoteException; method public final void setNotificationsShown(java.lang.String[]); field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications"; field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 diff --git a/api/system-current.txt b/api/system-current.txt index d7393b738d140..fa3eaa2e184d4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -35694,6 +35694,8 @@ package android.service.notification { method public void registerAsSystemService(android.content.Context, android.content.ComponentName, int) throws android.os.RemoteException; method public final void requestInterruptionFilter(int); method public final void requestListenerHints(int); + method public static final void requestRebind(android.content.ComponentName) throws android.os.RemoteException; + method public final void requestUnbind() throws android.os.RemoteException; method public final void setNotificationsShown(java.lang.String[]); method public final void setOnNotificationPostedTrim(int); method public void unregisterAsSystemService() throws android.os.RemoteException; diff --git a/api/test-current.txt b/api/test-current.txt index 9058fe7d8b888..3667ce5ad601f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -33552,6 +33552,8 @@ package android.service.notification { 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); + method public static final void requestRebind(android.content.ComponentName) throws android.os.RemoteException; + method public final void requestUnbind() throws android.os.RemoteException; method public final void setNotificationsShown(java.lang.String[]); field public static final java.lang.String CATEGORY_VR_NOTIFICATIONS = "android.intent.category.vr.notifications"; field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1 diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index e60cb0377e962..633f6995b9863 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -68,6 +68,9 @@ interface INotificationManager void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id); void cancelNotificationsFromListener(in INotificationListener token, in String[] keys); + void requestBindListener(in ComponentName component); + void requestUnbindListener(in INotificationListener token); + void setNotificationsShownFromListener(in INotificationListener token, in String[] keys); ParceledListSlice getActiveNotificationsFromListener(in INotificationListener token, in String[] keys, int trim); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 3a8956ec15d9f..aba82fad0bfa7 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -17,13 +17,18 @@ package android.service.notification; import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.INotificationManager; import android.app.Notification; +import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; /** diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 6c99489d77090..47a3f5efedbf5 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -690,6 +690,36 @@ public abstract class NotificationListenerService extends Service { } } + /** + * Request that the listener be rebound, after a previous call to (@link requestUnbind). + * + *

This method will fail for assistants that have + * not been granted the permission by the user. + * + *

The service should wait for the {@link #onListenerConnected()} event + * before performing any operations. + */ + public static final void requestRebind(ComponentName componentName) + throws RemoteException { + INotificationManager noMan = INotificationManager.Stub.asInterface( + ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + noMan.requestBindListener(componentName); + } + + /** + * Request that the service be unbound. + * + *

This will no longer receive updates until + * {@link #requestRebind(ComponentName)} is called. + * The service will likely be kiled by the system after this call. + */ + public final void requestUnbind() throws RemoteException { + if (mWrapper != null) { + INotificationManager noMan = getNotificationInterface(); + noMan.requestUnbindListener(mWrapper); + } + } + /** Convert new-style Icons to legacy representations for pre-M clients. */ private void createLegacyIconExtras(Notification n) { Icon smallIcon = n.getSmallIcon(); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 09e66475c1be0..f360dc2c42830 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -86,7 +86,7 @@ abstract public class ManagedServices { protected final ArrayList mServices = new ArrayList(); // things that will be put into mServices as soon as they're ready private final ArrayList mServicesBinding = new ArrayList(); - // lists the component names of all enabled (and therefore connected) + // lists the component names of all enabled (and therefore potentially connected) // app services for current profiles. private ArraySet mEnabledServicesForCurrentProfiles = new ArraySet(); @@ -97,6 +97,8 @@ abstract public class ManagedServices { private ArraySet mRestoredPackages = new ArraySet<>(); // State of current service categories private ArrayMap mCategoryEnabled = new ArrayMap<>(); + // List of enabled packages that have nevertheless asked not to be run + private ArraySet mSnoozingForCurrentProfiles = new ArraySet<>(); // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a @@ -174,6 +176,12 @@ abstract public class ManagedServices { + (info.isSystem?" SYSTEM":"") + (info.isGuest(this)?" GUEST":"")); } + + pw.println(" Snoozed " + getCaption() + "s (" + + mSnoozingForCurrentProfiles.size() + "):"); + for (ComponentName name : mSnoozingForCurrentProfiles) { + pw.println(" " + name.flattenToShortString()); + } } // By convention, restored settings are replicated to another settings @@ -242,14 +250,25 @@ abstract public class ManagedServices { rebindServices(); } - public ManagedServiceInfo checkServiceTokenLocked(IInterface service) { - checkNotNull(service); + public ManagedServiceInfo getServiceFromTokenLocked(IInterface service) { + if (service == null) { + return null; + } final IBinder token = service.asBinder(); final int N = mServices.size(); for (int i = 0; i < N; i++) { final ManagedServiceInfo info = mServices.get(i); if (info.service.asBinder() == token) return info; } + return null; + } + + public ManagedServiceInfo checkServiceTokenLocked(IInterface service) { + checkNotNull(service); + ManagedServiceInfo info = getServiceFromTokenLocked(service); + if (info != null) { + return info; + } throw new SecurityException("Disallowed call from unknown " + getCaption() + ": " + service); } @@ -278,6 +297,35 @@ abstract public class ManagedServices { checkType(guest.service); if (registerServiceImpl(guest) != null) { onServiceAdded(guest); + onServiceAdded(guest); + } + } + + public void setComponentState(ComponentName component, boolean enabled) { + boolean previous = !mSnoozingForCurrentProfiles.contains(component); + if (previous == enabled) { + return; + } + + if (enabled) { + mSnoozingForCurrentProfiles.remove(component); + } else { + mSnoozingForCurrentProfiles.add(component); + } + + // State changed + if (DEBUG) { + Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " + + component.flattenToShortString()); + } + + final int[] userIds = mUserProfiles.getCurrentProfileIds(); + for (int userId : userIds) { + if (enabled) { + registerServiceLocked(component, userId); + } else { + unregisterServiceLocked(component, userId); + } } } @@ -324,6 +372,7 @@ abstract public class ManagedServices { private void rebuildRestoredPackages() { mRestoredPackages.clear(); + mSnoozingForCurrentProfiles.clear(); String settingName = restoredSettingName(mConfig); int[] userIds = mUserProfiles.getCurrentProfileIds(); final int N = userIds.length; @@ -525,6 +574,7 @@ abstract public class ManagedServices { add.removeAll(c); } } + add.removeAll(mSnoozingForCurrentProfiles); toAdd.put(userIds[i], add); @@ -803,6 +853,10 @@ abstract public class ManagedServices { return ManagedServices.this != host; } + public ManagedServices getOwner() { + return ManagedServices.this; + } + @Override public String toString() { return new StringBuilder("ManagedServiceInfo[") @@ -846,6 +900,11 @@ abstract public class ManagedServices { } } + /** convenience method for looking in mEnabledServicesForCurrentProfiles */ + public boolean isComponentEnabledForCurrentProfiles(ComponentName component) { + return mEnabledServicesForCurrentProfiles.contains(component); + } + public static class UserProfiles { // Profiles of the current user. private final SparseArray mCurrentProfiles = new SparseArray(); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 82c38af677ced..1c49440c96b25 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1445,6 +1445,37 @@ public class NotificationManagerService extends SystemService { } } + /** + * Handle request from an approved listener to re-enable itself. + * + * @param component The componenet to be re-enabled, caller must match package. + */ + @Override + public void requestBindListener(ComponentName component) { + checkCallerIsSystemOrSameApp(component.getPackageName()); + long identity = Binder.clearCallingIdentity(); + try { + ManagedServices manager = mAssistant.isComponentEnabledForCurrentProfiles(component) + ? mAssistant + : mListeners; + manager.setComponentState(component, true); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void requestUnbindListener(INotificationListener token) { + long identity = Binder.clearCallingIdentity(); + try { + // allow bound services to disable themselves + final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token); + info.getOwner().setComponentState(info.component, false); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public void setNotificationsShownFromListener(INotificationListener token, String[] keys) { long identity = Binder.clearCallingIdentity(); @@ -3432,6 +3463,7 @@ public class NotificationManagerService extends SystemService { } } + @Override protected void onServiceRemovedLocked(ManagedServiceInfo removed) { if (mListenersDisablingEffects.remove(removed)) {