diff options
15 files changed, 780 insertions, 362 deletions
@@ -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 \ diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 8681f5c..045fab1 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -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); }
\ No newline at end of file diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 691317b..b578b48 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -723,6 +723,13 @@ public final class 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> * In some cases, a matching Activity may not exist, so ensure you safeguard @@ -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"; diff --git a/core/java/android/service/notification/Condition.aidl b/core/java/android/service/notification/Condition.aidl new file mode 100644 index 0000000..432852c --- /dev/null +++ b/core/java/android/service/notification/Condition.aidl @@ -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; + diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java new file mode 100644 index 0000000..cfd40f3 --- /dev/null +++ b/core/java/android/service/notification/Condition.java @@ -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]; + } + }; +} diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java new file mode 100644 index 0000000..8777e50 --- /dev/null +++ b/core/java/android/service/notification/ConditionProviderService.java @@ -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> + * <service android:name=".MyConditionProvider" + * android:label="@string/service_name" + * android:permission="android.permission.BIND_CONDITION_PROVIDER_SERVICE"> + * <intent-filter> + * <action android:name="android.service.notification.ConditionProviderService" /> + * </intent-filter> + * </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; + } + } + } +} diff --git a/core/java/android/service/notification/IConditionProvider.aidl b/core/java/android/service/notification/IConditionProvider.aidl new file mode 100644 index 0000000..cb582da --- /dev/null +++ b/core/java/android/service/notification/IConditionProvider.aidl @@ -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); +}
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8dfce64..4f093a8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -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). --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 9b89eaa..cb52db2 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2054,6 +2054,11 @@ <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. --> <string name="permdesc_invokeCarrierSetup">Allows the holder to invoke the carrier-provided configuration app. Should never be needed for normal apps.</string> @@ -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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bb0d184..431dab8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -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" /> diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java new file mode 100644 index 0000000..a282270 --- /dev/null +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationListeners.java b/services/core/java/com/android/server/notification/ManagedServices.java index 91d2f98..81b28e8 100644 --- a/services/core/java/com/android/server/notification/NotificationListeners.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -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); } - public void onBootPhaseAppsCanStart() { - mSettingsObserver.observe(); + abstract protected Config getConfig(); + + private String getCaption() { + return mConfig.caption; } - protected void onServiceAdded(INotificationListener mListener) { - // for subclasses + 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(); } 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; + } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5f096cb..5a1f9b2 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -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,17 +1330,23 @@ 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) != PackageManager.PERMISSION_GRANTED) { @@ -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 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 isUidSystem(int uid) { + final int appid = UserHandle.getAppId(uid); + return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); + } + + 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); + } + }); + } + } + + 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); } } - public boolean isCurrentProfile(int userId) { - synchronized (mCurrentProfiles) { - return mCurrentProfiles.get(userId) != null; + 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); } } } diff --git a/services/core/java/com/android/server/notification/NotificationUtil.java b/services/core/java/com/android/server/notification/NotificationUtil.java deleted file mode 100644 index 459adce..0000000 --- a/services/core/java/com/android/server/notification/NotificationUtil.java +++ /dev/null @@ -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); - } - } -} diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 80f5b5c..b00beb6 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -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); } |