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
This commit is contained in:
Chris Wren
2016-01-04 18:01:27 -05:00
parent 10b89dc531
commit ab41eecf22
8 changed files with 138 additions and 3 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
/**

View File

@@ -690,6 +690,36 @@ public abstract class NotificationListenerService extends Service {
}
}
/**
* Request that the listener be rebound, after a previous call to (@link requestUnbind).
*
* <P>This method will fail for assistants that have
* not been granted the permission by the user.
*
* <P>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.
*
* <P>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();

View File

@@ -86,7 +86,7 @@ abstract public class ManagedServices {
protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
// things that will be put into mServices as soon as they're ready
private final ArrayList<String> mServicesBinding = new ArrayList<String>();
// 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<ComponentName> mEnabledServicesForCurrentProfiles
= new ArraySet<ComponentName>();
@@ -97,6 +97,8 @@ abstract public class ManagedServices {
private ArraySet<String> mRestoredPackages = new ArraySet<>();
// State of current service categories
private ArrayMap<String, Boolean> mCategoryEnabled = new ArrayMap<>();
// List of enabled packages that have nevertheless asked not to be run
private ArraySet<ComponentName> 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<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();

View File

@@ -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)) {