Merge "Introduce condition provider services."

This commit is contained in:
John Spurlock
2014-04-25 18:19:06 +00:00
committed by Android (Google) Code Review
15 changed files with 779 additions and 361 deletions

View File

@ -180,6 +180,7 @@ LOCAL_SRC_FILES += \
core/java/android/os/IUserManager.aidl \
core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/service/notification/IConditionProvider.aidl \
core/java/android/print/ILayoutResultCallback.aidl \
core/java/android/print/IPrinterDiscoveryObserver.aidl \
core/java/android/print/IPrintDocumentAdapter.aidl \

View File

@ -22,6 +22,8 @@ import android.service.notification.StatusBarNotification;
import android.app.Notification;
import android.content.ComponentName;
import android.content.Intent;
import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
import android.service.notification.ZenModeConfig;
@ -53,4 +55,5 @@ interface INotificationManager
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
void notifyCondition(in IConditionProvider provider, in Condition condition);
}

View File

@ -722,6 +722,13 @@ public final class Settings {
public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS
= "android.settings.NOTIFICATION_LISTENER_SETTINGS";
/**
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CONDITION_PROVIDER_SETTINGS
= "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
/**
* Activity Action: Show settings for video captioning.
* <p>
@ -4516,6 +4523,11 @@ public final class Settings {
*/
public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
/**
* @hide
*/
public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
/** @hide */
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.notification;
parcelable Condition;

View File

@ -0,0 +1,119 @@
/**
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.notification;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Condition information from condition providers.
*
* @hide
*/
public class Condition implements Parcelable {
public static final int FLAG_RELEVANT_NOW = 1 << 0;
public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
public final Uri id;
public String caption;
public boolean state;
public int flags;
public Condition(Uri id, String caption, boolean state, int flags) {
if (id == null) throw new IllegalArgumentException("id is required");
if (caption == null) throw new IllegalArgumentException("caption is required");
this.id = id;
this.caption = caption;
this.state = state;
this.flags = flags;
}
private Condition(Parcel source) {
id = Uri.CREATOR.createFromParcel(source);
caption = source.readString();
state = source.readInt() == 1;
flags = source.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(id, 0);
dest.writeString(caption);
dest.writeInt(state ? 1 : 0);
dest.writeInt(flags);
}
@Override
public String toString() {
return new StringBuilder(Condition.class.getSimpleName()).append('[')
.append("id=").append(id)
.append(",caption=").append(caption)
.append(",state=").append(state)
.append(",flags=").append(flags)
.append(']').toString();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Condition)) return false;
if (o == this) return true;
final Condition other = (Condition) o;
return Objects.equals(other.id, id)
&& Objects.equals(other.caption, caption)
&& other.state == state
&& other.flags == flags;
}
@Override
public int hashCode() {
return Objects.hash(id, caption, state, flags);
}
@Override
public int describeContents() {
return 0;
}
public Condition copy() {
final Parcel parcel = Parcel.obtain();
try {
writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return new Condition(parcel);
} finally {
parcel.recycle();
}
}
public static final Parcelable.Creator<Condition> CREATOR
= new Parcelable.Creator<Condition>() {
@Override
public Condition createFromParcel(Parcel source) {
return new Condition(source);
}
@Override
public Condition[] newArray(int size) {
return new Condition[size];
}
};
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.notification;
import android.annotation.SdkConstant;
import android.app.INotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.os.ServiceManager;
import android.util.Log;
/**
* A service that provides conditions about boolean state.
* <p>To extend this class, you must declare the service in your manifest file with
* the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
* <pre>
* &lt;service android:name=".MyConditionProvider"
* android:label="&#64;string/service_name"
* android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE">
* &lt;intent-filter>
* &lt;action android:name="android.service.notification.ConditionProviderService" />
* &lt;/intent-filter>
* &lt;/service></pre>
*
* @hide
*/
public abstract class ConditionProviderService extends Service {
private final String TAG = ConditionProviderService.class.getSimpleName()
+ "[" + getClass().getSimpleName() + "]";
private Provider mProvider;
private INotificationManager mNoMan;
/**
* The {@link Intent} that must be declared as handled by the service.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE
= "android.service.notification.ConditionProviderService";
abstract public Condition[] queryConditions(int relevance);
abstract public Condition[] getConditions(Uri[] conditionIds);
abstract public boolean subscribe(Uri conditionId);
abstract public boolean unsubscribe(Uri conditionId);
private final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
return mNoMan;
}
public final void notifyCondition(Condition condition) {
if (!isBound()) return;
try {
getNotificationInterface().notifyCondition(mProvider, condition);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
}
@Override
public IBinder onBind(Intent intent) {
if (mProvider == null) {
mProvider = new Provider();
}
return mProvider;
}
private boolean isBound() {
if (mProvider == null) {
Log.w(TAG, "Condition provider service not yet bound.");
return false;
}
return true;
}
private final class Provider extends IConditionProvider.Stub {
private final ConditionProviderService mService = ConditionProviderService.this;
@Override
public Condition[] queryConditions(int relevance) {
try {
return mService.queryConditions(relevance);
} catch (Throwable t) {
Log.w(TAG, "Error running queryConditions", t);
return null;
}
}
@Override
public Condition[] getConditions(Uri[] conditionIds) {
try {
return mService.getConditions(conditionIds);
} catch (Throwable t) {
Log.w(TAG, "Error running getConditions", t);
return null;
}
}
@Override
public boolean subscribe(Uri conditionId) {
try {
return mService.subscribe(conditionId);
} catch (Throwable t) {
Log.w(TAG, "Error running subscribe", t);
return false;
}
}
@Override
public boolean unsubscribe(Uri conditionId) {
try {
return mService.unsubscribe(conditionId);
} catch (Throwable t) {
Log.w(TAG, "Error running unsubscribe", t);
return false;
}
}
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.notification;
import android.net.Uri;
import android.service.notification.Condition;
/** @hide */
interface IConditionProvider {
Condition[] queryConditions(int relevance);
Condition[] getConditions(in Uri[] conditionIds);
boolean subscribe(in Uri conditionId);
boolean unsubscribe(in Uri conditionId);
}

View File

@ -2627,6 +2627,15 @@
android:description="@string/permdesc_bindNotificationListenerService"
android:protectionLevel="signature" />
<!-- Must be required by an {@link
android.service.notification.ConditionProviderService},
to ensure that only the system can bind to it.
@hide -->
<permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE"
android:label="@string/permlab_bindConditionProviderService"
android:description="@string/permdesc_bindConditionProviderService"
android:protectionLevel="signature" />
<!-- Allows an application to call into a carrier setup flow. It is up to the
carrier setup application to enforce that this permission is required
@hide This is not a third-party API (intended for OEMs and system apps). -->

View File

@ -2053,6 +2053,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindNotificationListenerService">Allows the holder to bind to the top-level interface of a notification listener service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindConditionProviderService">bind to a condition provider service</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_bindConditionProviderService">Allows the holder to bind to the top-level interface of a condition provider service. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_invokeCarrierSetup">invoke the carrier-provided configuration app</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@ -3803,6 +3808,8 @@
<!-- Label to show for a service that is running because it is observing
the user's notifications. -->
<string name="notification_listener_binding_label">Notification listener</string>
<!-- Label to show for a service that is running because it is providing conditions. -->
<string name="condition_provider_service_binding_label">Condition provider</string>
<!-- Do Not Translate: Alternate eri.xml -->
<string name="alternate_eri_file">/data/eri.xml</string>

View File

@ -1580,6 +1580,7 @@
<java-symbol type="string" name="low_internal_storage_view_text" />
<java-symbol type="string" name="low_internal_storage_view_title" />
<java-symbol type="string" name="notification_listener_binding_label" />
<java-symbol type="string" name="condition_provider_service_binding_label" />
<java-symbol type="string" name="report" />
<java-symbol type="string" name="select_input_method" />
<java-symbol type="string" name="select_keyboard_layout_notification_title" />

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.notification;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.provider.Settings;
import android.service.notification.IConditionProvider;
import android.service.notification.ConditionProviderService;
import android.util.Slog;
import com.android.internal.R;
public class ConditionProviders extends ManagedServices {
public ConditionProviders(Context context, Handler handler,
Object mutex, UserProfiles userProfiles) {
super(context, handler, mutex, userProfiles);
}
@Override
protected Config getConfig() {
Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
c.clientLabel = R.string.condition_provider_service_binding_label;
return c;
}
@Override
protected IInterface asInterface(IBinder binder) {
return IConditionProvider.Stub.asInterface(binder);
}
@Override
protected void onServiceAdded(IInterface service) {
Slog.d(TAG, "onServiceAdded " + service);
}
}

View File

@ -16,6 +16,7 @@
package com.android.server.notification;
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
@ -24,191 +25,174 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.server.notification.NotificationManagerService.UserProfiles;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class NotificationListeners {
private static final String TAG = "NotificationListeners";
private static final boolean DBG = NotificationManagerService.DBG;
/**
* Manages the lifecycle of application-provided services bound by system server.
*
* Services managed by this helper must have:
* - An associated system settings value with a list of enabled component names.
* - A well-known action for services to use in their intent-filter.
* - A system permission for services to require in order to ensure system has exclusive binding.
* - A settings page for user configuration of enabled services, and associated intent action.
* - A remote interface definition (aidl) provided by the service used for communication.
*/
abstract public class ManagedServices {
protected final String TAG = getClass().getSimpleName();
protected static final boolean DEBUG = true;
private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
private static final String ENABLED_SERVICES_SEPARATOR = ":";
private final Context mContext;
private final Handler mHandler;
private final Object mMutex;
private final UserProfiles mUserProfiles;
private final SettingsObserver mSettingsObserver;
private final Config mConfig;
// contains connections to all connected listeners, including app services
// and system listeners
private final ArrayList<NotificationListenerInfo> mListeners
= new ArrayList<NotificationListenerInfo>();
// things that will be put into mListeners as soon as they're ready
// contains connections to all connected services, including app services
// and system services
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) listener
// lists the component names of all enabled (and therefore connected)
// app services for current profiles.
private ArraySet<ComponentName> mEnabledListenersForCurrentProfiles
private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
= new ArraySet<ComponentName>();
// Just the packages from mEnabledListenersForCurrentProfiles
private ArraySet<String> mEnabledListenerPackageNames = new ArraySet<String>();
// Just the packages from mEnabledServicesForCurrentProfiles
private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
public NotificationListeners(Context context, Handler handler, Object mutex,
public ManagedServices(Context context, Handler handler, Object mutex,
UserProfiles userProfiles) {
mContext = context;
mHandler = handler;
mMutex = mutex;
mUserProfiles = userProfiles;
mSettingsObserver = new SettingsObserver(mHandler);
mConfig = getConfig();
mSettingsObserver = new SettingsObserver(handler);
}
abstract protected Config getConfig();
private String getCaption() {
return mConfig.caption;
}
abstract protected IInterface asInterface(IBinder binder);
abstract protected void onServiceAdded(IInterface service);
private ManagedServiceInfo newServiceInfo(IInterface service,
ComponentName component, int userid, boolean isSystem, ServiceConnection connection,
int targetSdkVersion) {
return new ManagedServiceInfo(service, component, userid, isSystem, connection,
targetSdkVersion);
}
public void onBootPhaseAppsCanStart() {
mSettingsObserver.observe();
}
protected void onServiceAdded(INotificationListener mListener) {
// for subclasses
}
public void dump(PrintWriter pw) {
pw.println(" Listeners (" + mEnabledListenersForCurrentProfiles.size()
pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
+ ") enabled for current profiles:");
for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) {
for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
pw.println(" " + cmpt);
}
pw.println(" Live listeners (" + mListeners.size() + "):");
for (NotificationListenerInfo info : mListeners) {
pw.println(" Live " + getCaption() + "s (" + mServices.size() + "):");
for (ManagedServiceInfo info : mServices) {
pw.println(" " + info.component
+ " (user " + info.userid + "): " + info.listener
+ " (user " + info.userid + "): " + info.service
+ (info.isSystem?" SYSTEM":""));
}
}
public void onPackagesChanged(boolean queryReplace, String[] pkgList) {
boolean anyListenersInvolved = false;
boolean anyServicesInvolved = false;
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
if (mEnabledListenerPackageNames.contains(pkgName)) {
anyListenersInvolved = true;
if (mEnabledServicesPackageNames.contains(pkgName)) {
anyServicesInvolved = true;
}
}
}
if (anyListenersInvolved) {
if (anyServicesInvolved) {
// if we're not replacing a package, clean up orphaned bits
if (!queryReplace) {
disableNonexistentListeners();
disableNonexistentServices();
}
// make sure we're still bound to any of our
// listeners who may have just upgraded
rebindListenerServices();
// make sure we're still bound to any of our services who may have just upgraded
rebindServices();
}
}
/**
* asynchronously notify all listeners about a new notification
*/
public void notifyPostedLocked(StatusBarNotification sbn) {
// make a copy in case changes are made to the underlying Notification object
final StatusBarNotification sbnClone = sbn.clone();
for (final NotificationListenerInfo info : mListeners) {
mHandler.post(new Runnable() {
@Override
public void run() {
info.notifyPostedIfUserMatch(sbnClone);
}
});
}
}
/**
* asynchronously notify all listeners about a removed notification
*/
public void notifyRemovedLocked(StatusBarNotification sbn) {
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
final StatusBarNotification sbnLight = sbn.cloneLight();
for (final NotificationListenerInfo info : mListeners) {
mHandler.post(new Runnable() {
@Override
public void run() {
info.notifyRemovedIfUserMatch(sbnLight);
}
});
}
}
public NotificationListenerInfo checkListenerTokenLocked(INotificationListener listener) {
checkNullListener(listener);
final IBinder token = listener.asBinder();
final int N = mListeners.size();
public ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
checkNotNull(service);
final IBinder token = service.asBinder();
final int N = mServices.size();
for (int i=0; i<N; i++) {
final NotificationListenerInfo info = mListeners.get(i);
if (info.listener.asBinder() == token) return info;
final ManagedServiceInfo info = mServices.get(i);
if (info.service.asBinder() == token) return info;
}
throw new SecurityException("Disallowed call from unknown listener: " + listener);
throw new SecurityException("Disallowed call from unknown " + getCaption() + ": "
+ service);
}
public void unregisterListener(INotificationListener listener, int userid) {
checkNullListener(listener);
// no need to check permissions; if your listener binder is in the list,
public void unregisterService(IInterface service, int userid) {
checkNotNull(service);
// no need to check permissions; if your service binder is in the list,
// that's proof that you had permission to add it in the first place
unregisterListenerImpl(listener, userid);
unregisterServiceImpl(service, userid);
}
public void registerListener(INotificationListener listener,
ComponentName component, int userid) {
checkNullListener(listener);
registerListenerImpl(listener, component, userid);
public void registerService(IInterface service, ComponentName component, int userid) {
checkNotNull(service);
registerServiceImpl(service, component, userid);
}
/**
* Remove notification access for any services that no longer exist.
* Remove access for any services that no longer exist.
*/
private void disableNonexistentListeners() {
private void disableNonexistentServices() {
int[] userIds = mUserProfiles.getCurrentProfileIds();
final int N = userIds.length;
for (int i = 0 ; i < N; ++i) {
disableNonexistentListeners(userIds[i]);
disableNonexistentServices(userIds[i]);
}
}
private void disableNonexistentListeners(int userId) {
private void disableNonexistentServices(int userId) {
String flatIn = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
mConfig.secureSettingName,
userId);
if (!TextUtils.isEmpty(flatIn)) {
if (DBG) Slog.v(TAG, "flat before: " + flatIn);
if (DEBUG) Slog.v(TAG, "flat before: " + flatIn);
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
new Intent(NotificationListenerService.SERVICE_INTERFACE),
new Intent(mConfig.serviceInterface),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
userId);
@ -217,12 +201,11 @@ public class NotificationListeners {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
info.permission)) {
Slog.w(TAG, "Skipping notification listener service "
if (!mConfig.bindPermission.equals(info.permission)) {
Slog.w(TAG, "Skipping " + getCaption() + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
+ android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
+ mConfig.bindPermission);
continue;
}
installed.add(new ComponentName(info.packageName, info.name));
@ -230,7 +213,7 @@ public class NotificationListeners {
String flatOut = "";
if (!installed.isEmpty()) {
String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
String[] enabled = flatIn.split(ENABLED_SERVICES_SEPARATOR);
ArrayList<String> remaining = new ArrayList<String>(enabled.length);
for (int i = 0; i < enabled.length; i++) {
ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
@ -238,22 +221,23 @@ public class NotificationListeners {
remaining.add(enabled[i]);
}
}
flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
flatOut = TextUtils.join(ENABLED_SERVICES_SEPARATOR, remaining);
}
if (DBG) Slog.v(TAG, "flat after: " + flatOut);
if (DEBUG) Slog.v(TAG, "flat after: " + flatOut);
if (!flatIn.equals(flatOut)) {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
mConfig.secureSettingName,
flatOut, userId);
}
}
}
/**
* Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
* Called whenever packages change, the user switches, or the secure setting
* is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
*/
private void rebindListenerServices() {
private void rebindServices() {
if (DEBUG) Slog.d(TAG, "rebindServices");
final int[] userIds = mUserProfiles.getCurrentProfileIds();
final int nUserIds = userIds.length;
@ -262,17 +246,17 @@ public class NotificationListeners {
for (int i = 0; i < nUserIds; ++i) {
flat.put(userIds[i], Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
mConfig.secureSettingName,
userIds[i]));
}
NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
ManagedServiceInfo[] toRemove = new ManagedServiceInfo[mServices.size()];
final SparseArray<ArrayList<ComponentName>> toAdd
= new SparseArray<ArrayList<ComponentName>>();
synchronized (mMutex) {
// unbind and remove all existing listeners
toRemove = mListeners.toArray(toRemove);
// unbind and remove all existing services
toRemove = mServices.toArray(toRemove);
final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>();
final ArraySet<String> newPackages = new ArraySet<String>();
@ -284,7 +268,7 @@ public class NotificationListeners {
// decode the list of components
String toDecode = flat.get(userIds[i]);
if (toDecode != null) {
String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR);
for (int j = 0; j < components.length; j++) {
final ComponentName component
= ComponentName.unflattenFromString(components[j]);
@ -297,16 +281,16 @@ public class NotificationListeners {
}
}
mEnabledListenersForCurrentProfiles = newEnabled;
mEnabledListenerPackageNames = newPackages;
mEnabledServicesForCurrentProfiles = newEnabled;
mEnabledServicesPackageNames = newPackages;
}
for (NotificationListenerInfo info : toRemove) {
for (ManagedServiceInfo info : toRemove) {
final ComponentName component = info.component;
final int oldUser = info.userid;
Slog.v(TAG, "disabling notification listener for user "
Slog.v(TAG, "disabling " + getCaption() + " for user "
+ oldUser + ": " + component);
unregisterListenerService(component, info.userid);
unregisterService(component, info.userid);
}
for (int i = 0; i < nUserIds; ++i) {
@ -314,23 +298,18 @@ public class NotificationListeners {
final int N = add.size();
for (int j = 0; j < N; j++) {
final ComponentName component = add.get(j);
Slog.v(TAG, "enabling notification listener for user " + userIds[i] + ": "
Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": "
+ component);
registerListenerService(component, userIds[i]);
registerService(component, userIds[i]);
}
}
}
/**
* Version of registerListener that takes the name of a
* {@link android.service.notification.NotificationListenerService} to bind to.
*
* This is the mechanism by which third parties may subscribe to notifications.
* Version of registerService that takes the name of a service component to bind to.
*/
private void registerListenerService(final ComponentName name, final int userid) {
NotificationUtil.checkCallerIsSystem();
if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
private void registerService(final ComponentName name, final int userid) {
if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
synchronized (mMutex) {
final String servicesBindingTag = name.toString() + "/" + userid;
@ -340,28 +319,28 @@ public class NotificationListeners {
}
mServicesBinding.add(servicesBindingTag);
final int N = mListeners.size();
final int N = mServices.size();
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
final ManagedServiceInfo info = mServices.get(i);
if (name.equals(info.component)
&& info.userid == userid) {
// cut old connections
if (DBG) Slog.v(TAG, " disconnecting old listener: " + info.listener);
mListeners.remove(i);
if (DEBUG) Slog.v(TAG, " disconnecting old " + getCaption() + ": "
+ info.service);
mServices.remove(i);
if (info.connection != null) {
mContext.unbindService(info.connection);
}
}
}
Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
Intent intent = new Intent(mConfig.serviceInterface);
intent.setComponent(name);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
final PendingIntent pendingIntent = PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0);
mContext, 0, new Intent(mConfig.settingsAction), 0);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
ApplicationInfo appInfo = null;
@ -375,72 +354,68 @@ public class NotificationListeners {
appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
try {
if (DBG) Slog.v(TAG, "binding: " + intent);
if (DEBUG) Slog.v(TAG, "binding: " + intent);
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
IInterface mService;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
public void onServiceConnected(ComponentName name, IBinder binder) {
boolean added = false;
synchronized (mMutex) {
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info
= new NotificationListenerInfo(
mListener, name, userid, this,
targetSdkVersion);
service.linkToDeath(info, 0);
added = mListeners.add(info);
mService = asInterface(binder);
ManagedServiceInfo info = newServiceInfo(mService, name,
userid, false /*isSystem*/, this, targetSdkVersion);
binder.linkToDeath(info, 0);
added = mServices.add(info);
} catch (RemoteException e) {
// already dead
}
}
if (added) {
onServiceAdded(mListener);
onServiceAdded(mService);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
Slog.v(TAG, getCaption() + " connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
{
mServicesBinding.remove(servicesBindingTag);
Slog.w(TAG, "Unable to bind listener service: " + intent);
Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
return;
}
} catch (SecurityException ex) {
Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
return;
}
}
}
/**
* Remove a listener service for the given user by ComponentName
* Remove a service for the given user by ComponentName
*/
private void unregisterListenerService(ComponentName name, int userid) {
NotificationUtil.checkCallerIsSystem();
private void unregisterService(ComponentName name, int userid) {
synchronized (mMutex) {
final int N = mListeners.size();
final int N = mServices.size();
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
final ManagedServiceInfo info = mServices.get(i);
if (name.equals(info.component)
&& info.userid == userid) {
mListeners.remove(i);
mServices.remove(i);
if (info.connection != null) {
try {
mContext.unbindService(info.connection);
} catch (IllegalArgumentException ex) {
// something happened to the service: we think we have a connection
// but it's bogus.
Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
}
}
}
@ -449,41 +424,39 @@ public class NotificationListeners {
}
/**
* Removes a listener from the list but does not unbind from the listener's service.
* Removes a service from the list but does not unbind
*
* @return the removed listener.
* @return the removed service.
*/
private NotificationListenerInfo removeListenerImpl(
final INotificationListener listener, final int userid) {
NotificationListenerInfo listenerInfo = null;
private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) {
ManagedServiceInfo serviceInfo = null;
synchronized (mMutex) {
final int N = mListeners.size();
final int N = mServices.size();
for (int i=N-1; i>=0; i--) {
final NotificationListenerInfo info = mListeners.get(i);
if (info.listener.asBinder() == listener.asBinder()
final ManagedServiceInfo info = mServices.get(i);
if (info.service.asBinder() == service.asBinder()
&& info.userid == userid) {
listenerInfo = mListeners.remove(i);
serviceInfo = mServices.remove(i);
}
}
}
return listenerInfo;
return serviceInfo;
}
private void checkNullListener(INotificationListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Listener must not be null");
private void checkNotNull(IInterface service) {
if (service == null) {
throw new IllegalArgumentException(getCaption() + " must not be null");
}
}
private void registerListenerImpl(final INotificationListener listener,
private void registerServiceImpl(final IInterface service,
final ComponentName component, final int userid) {
synchronized (mMutex) {
try {
NotificationListenerInfo info
= new NotificationListenerInfo(listener, component, userid,
/*isSystem*/ true, Build.VERSION_CODES.L);
listener.asBinder().linkToDeath(info, 0);
mListeners.add(info);
ManagedServiceInfo info = newServiceInfo(service, component, userid,
true /*isSystem*/, null, Build.VERSION_CODES.L);
service.asBinder().linkToDeath(info, 0);
mServices.add(info);
} catch (RemoteException e) {
// already dead
}
@ -491,18 +464,17 @@ public class NotificationListeners {
}
/**
* Removes a listener from the list and unbinds from its service.
* Removes a service from the list and unbinds.
*/
private void unregisterListenerImpl(final INotificationListener listener, final int userid) {
NotificationListenerInfo info = removeListenerImpl(listener, userid);
private void unregisterServiceImpl(IInterface service, int userid) {
ManagedServiceInfo info = removeServiceImpl(service, userid);
if (info != null && info.connection != null) {
mContext.unbindService(info.connection);
}
}
private class SettingsObserver extends ContentObserver {
private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
= Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
private SettingsObserver(Handler handler) {
super(handler);
@ -510,7 +482,7 @@ public class NotificationListeners {
private void observe() {
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
resolver.registerContentObserver(mSecureSettingsUri,
false, this, UserHandle.USER_ALL);
update(null);
}
@ -521,42 +493,31 @@ public class NotificationListeners {
}
private void update(Uri uri) {
if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
rebindListenerServices();
if (uri == null || mSecureSettingsUri.equals(uri)) {
rebindServices();
}
}
}
public class NotificationListenerInfo implements IBinder.DeathRecipient {
public INotificationListener listener;
public class ManagedServiceInfo implements IBinder.DeathRecipient {
public IInterface service;
public ComponentName component;
public int userid;
public boolean isSystem;
public ServiceConnection connection;
public int targetSdkVersion;
public NotificationListenerInfo(INotificationListener listener, ComponentName component,
int userid, boolean isSystem, int targetSdkVersion) {
this.listener = listener;
public ManagedServiceInfo(IInterface service, ComponentName component,
int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
this.service = service;
this.component = component;
this.userid = userid;
this.isSystem = isSystem;
this.connection = null;
this.targetSdkVersion = targetSdkVersion;
}
public NotificationListenerInfo(INotificationListener listener, ComponentName component,
int userid, ServiceConnection connection, int targetSdkVersion) {
this.listener = listener;
this.component = component;
this.userid = userid;
this.isSystem = false;
this.connection = connection;
this.targetSdkVersion = targetSdkVersion;
}
public boolean enabledAndUserMatches(StatusBarNotification sbn) {
final int nid = sbn.getUserId();
public boolean enabledAndUserMatches(int nid) {
if (!isEnabledForCurrentProfiles()) {
return false;
}
@ -569,40 +530,65 @@ public class NotificationListeners {
return targetSdkVersion >= Build.VERSION_CODES.L;
}
public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
if (!enabledAndUserMatches(sbn)) {
return;
}
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
if (!enabledAndUserMatches(sbn)) return;
try {
listener.onNotificationRemoved(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
@Override
public void binderDied() {
// Remove the listener, but don't unbind from the service. The system will bring the
// service back up, and the onServiceConnected handler will readd the listener with the
// Remove the service, but don't unbind from the service. The system will bring the
// service back up, and the onServiceConnected handler will readd the service with the
// new binding. If this isn't a bound service, and is just a registered
// INotificationListener, just removing it from the list is all we need to do anyway.
removeListenerImpl(this.listener, this.userid);
// service, just removing it from the list is all we need to do anyway.
removeServiceImpl(this.service, this.userid);
}
/** convenience method for looking in mEnabledListenersForCurrentProfiles */
/** convenience method for looking in mEnabledServicesForCurrentProfiles */
public boolean isEnabledForCurrentProfiles() {
if (this.isSystem) return true;
if (this.connection == null) return false;
return mEnabledListenersForCurrentProfiles.contains(this.component);
return mEnabledServicesForCurrentProfiles.contains(this.component);
}
}
public static class UserProfiles {
// Profiles of the current user.
private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
public void updateCache(Context context) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (userManager != null) {
int currentUserId = ActivityManager.getCurrentUser();
List<UserInfo> profiles = userManager.getProfiles(currentUserId);
synchronized (mCurrentProfiles) {
mCurrentProfiles.clear();
for (UserInfo user : profiles) {
mCurrentProfiles.put(user.id, user);
}
}
}
}
public int[] getCurrentProfileIds() {
synchronized (mCurrentProfiles) {
int[] users = new int[mCurrentProfiles.size()];
final int N = mCurrentProfiles.size();
for (int i = 0; i < N; ++i) {
users[i] = mCurrentProfiles.keyAt(i);
}
return users;
}
}
public boolean isCurrentProfile(int userId) {
synchronized (mCurrentProfiles) {
return mCurrentProfiles.get(userId) != null;
}
}
}
protected static class Config {
String caption;
String serviceInterface;
String secureSettingName;
String bindPermission;
String settingsAction;
int clientLabel;
}
}

View File

@ -22,6 +22,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
@ -35,10 +36,10 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@ -49,15 +50,18 @@ import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.INotificationListener;
import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@ -65,7 +69,6 @@ import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@ -78,7 +81,8 @@ import com.android.server.EventLogTags;
import com.android.server.SystemService;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
import com.android.server.notification.NotificationListeners.NotificationListenerInfo;
import com.android.server.notification.ManagedServices.ManagedServiceInfo;
import com.android.server.notification.ManagedServices.UserProfiles;
import com.android.server.notification.NotificationUsageStats.SingleNotificationStats;
import com.android.server.statusbar.StatusBarManagerInternal;
@ -101,7 +105,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/** {@hide} */
@ -191,8 +194,9 @@ public class NotificationManagerService extends SystemService {
final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
private NotificationListeners mListeners;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
private ConditionProviders mConditionProviders;
private final NotificationUsageStats mUsageStats = new NotificationUsageStats();
@ -745,6 +749,7 @@ public class NotificationManagerService extends SystemService {
}
}
mListeners.onPackagesChanged(queryReplace, pkgList);
mConditionProviders.onPackagesChanged(queryReplace, pkgList);
} else if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@ -845,18 +850,9 @@ public class NotificationManagerService extends SystemService {
importOldBlockDb();
mListeners = new NotificationListeners(getContext(),
mHandler, mNotificationList, mUserProfiles) {
@Override
public void onServiceAdded(INotificationListener listener) {
final String[] keys = getActiveNotificationKeysFromListener(listener);
try {
listener.onListenerConnected(keys);
} catch (RemoteException e) {
// we tried
}
}
};
mListeners = new NotificationListeners();
mConditionProviders = new ConditionProviders(getContext(),
mHandler, mNotificationList, mUserProfiles);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@ -972,6 +968,7 @@ public class NotificationManagerService extends SystemService {
// bind to listener services.
mSettingsObserver.observe();
mListeners.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
}
}
@ -1005,8 +1002,7 @@ public class NotificationManagerService extends SystemService {
return ;
}
final boolean isSystemToast =
NotificationUtil.isCallerSystem() || ("android".equals(pkg));
final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
if (!isSystemToast) {
@ -1097,7 +1093,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
// Don't allow client applications to cancel foreground service notis.
@ -1109,7 +1105,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void cancelAllNotifications(String pkg, int userId) {
NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
@ -1123,7 +1119,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
NotificationUtil.checkCallerIsSystem();
checkCallerIsSystem();
setNotificationsEnabledForPackageImpl(pkg, uid, enabled);
}
@ -1133,7 +1129,7 @@ public class NotificationManagerService extends SystemService {
*/
@Override
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
NotificationUtil.checkCallerIsSystem();
checkCallerIsSystem();
return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
== AppOpsManager.MODE_ALLOWED);
}
@ -1201,8 +1197,8 @@ public class NotificationManagerService extends SystemService {
@Override
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
NotificationUtil.checkCallerIsSystem();
mListeners.registerListener(listener, component, userid);
checkCallerIsSystem();
mListeners.registerService(listener, component, userid);
}
/**
@ -1210,7 +1206,7 @@ public class NotificationManagerService extends SystemService {
*/
@Override
public void unregisterListener(INotificationListener listener, int userid) {
mListeners.unregisterListener(listener, userid);
mListeners.unregisterService(listener, userid);
}
/**
@ -1227,8 +1223,7 @@ public class NotificationManagerService extends SystemService {
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
final NotificationListenerInfo info =
mListeners.checkListenerTokenLocked(token);
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (keys != null) {
final int N = keys.length;
for (int i = 0; i < N; i++) {
@ -1237,7 +1232,7 @@ public class NotificationManagerService extends SystemService {
if (userId != info.userid && userId != UserHandle.USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
throw new SecurityException("Disallowed call from listener: "
+ info.listener);
+ info.service);
}
if (r != null) {
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
@ -1255,7 +1250,7 @@ public class NotificationManagerService extends SystemService {
}
}
private void cancelNotificationFromListenerLocked(NotificationListenerInfo info,
private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId) {
cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
@ -1278,8 +1273,7 @@ public class NotificationManagerService extends SystemService {
long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationList) {
final NotificationListenerInfo info =
mListeners.checkListenerTokenLocked(token);
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
if (info.supportsProfiles()) {
Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) "
+ "from " + info.component
@ -1305,14 +1299,14 @@ public class NotificationManagerService extends SystemService {
public StatusBarNotification[] getActiveNotificationsFromListener(
INotificationListener token, String[] keys) {
synchronized (mNotificationList) {
final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token);
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<StatusBarNotification> list
= new ArrayList<StatusBarNotification>();
if (keys == null) {
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
StatusBarNotification sbn = mNotificationList.get(i).sbn;
if (info.enabledAndUserMatches(sbn)) {
if (info.enabledAndUserMatches(sbn.getUserId())) {
list.add(sbn);
}
}
@ -1320,7 +1314,7 @@ public class NotificationManagerService extends SystemService {
final int N = keys.length;
for (int i=0; i<N; i++) {
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r != null && info.enabledAndUserMatches(r.sbn)) {
if (r != null && info.enabledAndUserMatches(r.sbn.getUserId())) {
list.add(r.sbn);
}
}
@ -1336,16 +1330,22 @@ public class NotificationManagerService extends SystemService {
@Override
public ZenModeConfig getZenModeConfig() {
NotificationUtil.checkCallerIsSystem();
checkCallerIsSystem();
return mZenModeHelper.getConfig();
}
@Override
public boolean setZenModeConfig(ZenModeConfig config) {
NotificationUtil.checkCallerIsSystem();
checkCallerIsSystem();
return mZenModeHelper.setConfig(config);
}
@Override
public void notifyCondition(IConditionProvider provider, Condition condition) {
// TODO check token
mZenModeHelper.notifyCondition(condition);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@ -1362,12 +1362,12 @@ public class NotificationManagerService extends SystemService {
private String[] getActiveNotificationKeysFromListener(INotificationListener token) {
synchronized (mNotificationList) {
final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token);
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
final ArrayList<String> keys = new ArrayList<String>();
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final StatusBarNotification sbn = mNotificationList.get(i).sbn;
if (info.enabledAndUserMatches(sbn)) {
if (info.enabledAndUserMatches(sbn.getUserId())) {
keys.add(sbn.getKey());
}
}
@ -1379,6 +1379,7 @@ public class NotificationManagerService extends SystemService {
pw.println("Current Notification Manager state:");
mListeners.dump(pw);
mConditionProviders.dump(pw);
int N;
@ -1455,9 +1456,8 @@ public class NotificationManagerService extends SystemService {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
NotificationUtil.checkCallerIsSystemOrSameApp(pkg);
final boolean isSystemNotification =
NotificationUtil.isUidSystem(callingUid) || ("android".equals(pkg));
checkCallerIsSystemOrSameApp(pkg);
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
@ -2028,7 +2028,7 @@ public class NotificationManagerService extends SystemService {
void cancelNotification(final int callingUid, final int callingPid,
final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
final int userId, final int reason, final NotificationListenerInfo listener) {
final int userId, final int reason, final ManagedServiceInfo listener) {
// In enqueueNotificationInternal notifications are added by scheduling the
// work on the worker handler. Hence, we also schedule the cancel on this
// handler to avoid a scenario where an add notification call followed by a
@ -2099,7 +2099,7 @@ public class NotificationManagerService extends SystemService {
*/
boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
int mustNotHaveFlags, boolean doit, int userId, int reason,
NotificationListenerInfo listener) {
ManagedServiceInfo listener) {
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
pkg, userId, mustHaveFlags, mustNotHaveFlags, reason,
listener == null ? null : listener.component.toShortString());
@ -2141,7 +2141,7 @@ public class NotificationManagerService extends SystemService {
}
void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
NotificationListenerInfo listener, boolean includeCurrentProfiles) {
ManagedServiceInfo listener, boolean includeCurrentProfiles) {
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
null, userId, 0, 0, reason,
listener == null ? null : listener.component.toShortString());
@ -2234,38 +2234,129 @@ public class NotificationManagerService extends SystemService {
}
}
public static class UserProfiles {
// Profiles of the current user.
private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
private static boolean isUidSystem(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
}
private void updateCache(Context context) {
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
if (userManager != null) {
int currentUserId = ActivityManager.getCurrentUser();
List<UserInfo> profiles = userManager.getProfiles(currentUserId);
synchronized (mCurrentProfiles) {
mCurrentProfiles.clear();
for (UserInfo user : profiles) {
mCurrentProfiles.put(user.id, user);
private static boolean isCallerSystem() {
return isUidSystem(Binder.getCallingUid());
}
private static void checkCallerIsSystem() {
if (isCallerSystem()) {
return;
}
throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
}
private static void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallerSystem()) {
return;
}
final int uid = Binder.getCallingUid();
try {
ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
pkg, 0, UserHandle.getCallingUserId());
if (!UserHandle.isSameApp(ai.uid, uid)) {
throw new SecurityException("Calling uid " + uid + " gave package"
+ pkg + " which is owned by uid " + ai.uid);
}
} catch (RemoteException re) {
throw new SecurityException("Unknown package " + pkg + "\n" + re);
}
}
public class NotificationListeners extends ManagedServices {
public NotificationListeners() {
super(getContext(), mHandler, mNotificationList, mUserProfiles);
}
@Override
protected Config getConfig() {
Config c = new Config();
c.caption = "notification listener";
c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
c.clientLabel = R.string.notification_listener_binding_label;
return c;
}
@Override
protected IInterface asInterface(IBinder binder) {
return INotificationListener.Stub.asInterface(binder);
}
@Override
public void onServiceAdded(IInterface service) {
final INotificationListener listener = (INotificationListener) service;
final String[] keys = getActiveNotificationKeysFromListener(listener);
try {
listener.onListenerConnected(keys);
} catch (RemoteException e) {
// we tried
}
}
/**
* asynchronously notify all listeners about a new notification
*/
public void notifyPostedLocked(StatusBarNotification sbn) {
// make a copy in case changes are made to the underlying Notification object
final StatusBarNotification sbnClone = sbn.clone();
for (final ManagedServiceInfo info : mServices) {
mHandler.post(new Runnable() {
@Override
public void run() {
notifyPostedIfUserMatch(info, sbnClone);
}
}
});
}
}
public int[] getCurrentProfileIds() {
synchronized (mCurrentProfiles) {
int[] users = new int[mCurrentProfiles.size()];
final int N = mCurrentProfiles.size();
for (int i = 0; i < N; ++i) {
users[i] = mCurrentProfiles.keyAt(i);
}
return users;
/**
* asynchronously notify all listeners about a removed notification
*/
public void notifyRemovedLocked(StatusBarNotification sbn) {
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
// notification
final StatusBarNotification sbnLight = sbn.cloneLight();
for (ManagedServiceInfo serviceInfo : mServices) {
final ManagedServiceInfo info = (ManagedServiceInfo) serviceInfo;
mHandler.post(new Runnable() {
@Override
public void run() {
notifyRemovedIfUserMatch(info, sbnLight);
}
});
}
}
public boolean isCurrentProfile(int userId) {
synchronized (mCurrentProfiles) {
return mCurrentProfiles.get(userId) != null;
private void notifyPostedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn) {
if (!info.enabledAndUserMatches(sbn.getUserId())) {
return;
}
final INotificationListener listener = (INotificationListener)info.service;
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
private void notifyRemovedIfUserMatch(ManagedServiceInfo info, StatusBarNotification sbn) {
if (!info.enabledAndUserMatches(sbn.getUserId())) {
return;
}
final INotificationListener listener = (INotificationListener)info.service;
try {
listener.onNotificationRemoved(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
}

View File

@ -1,63 +0,0 @@
/**
* Copyright (c) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.notification;
import android.app.AppGlobals;
import android.content.pm.ApplicationInfo;
import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
public class NotificationUtil {
// Return true if the UID is a system or phone UID and therefore should not have
// any notifications or toasts blocked.
public static boolean isUidSystem(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
}
// same as isUidSystem(int, int) for the Binder caller's UID.
public static boolean isCallerSystem() {
return isUidSystem(Binder.getCallingUid());
}
public static void checkCallerIsSystem() {
if (isCallerSystem()) {
return;
}
throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
}
public static void checkCallerIsSystemOrSameApp(String pkg) {
if (isCallerSystem()) {
return;
}
final int uid = Binder.getCallingUid();
try {
ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
pkg, 0, UserHandle.getCallingUserId());
if (!UserHandle.isSameApp(ai.uid, uid)) {
throw new SecurityException("Calling uid " + uid + " gave package"
+ pkg + " which is owned by uid " + ai.uid);
}
} catch (RemoteException re) {
throw new SecurityException("Unknown package " + pkg + "\n" + re);
}
}
}

View File

@ -33,6 +33,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.util.Slog;
@ -201,6 +202,10 @@ public class ZenModeHelper {
return true;
}
public void notifyCondition(Condition condition) {
Slog.d(TAG, "notifyCondition " + condition);
}
private boolean isCall(String pkg, Notification n) {
return CALL_PACKAGES.contains(pkg);
}