summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--core/java/android/app/INotificationManager.aidl3
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/java/android/service/notification/Condition.aidl20
-rw-r--r--core/java/android/service/notification/Condition.java119
-rw-r--r--core/java/android/service/notification/ConditionProviderService.java141
-rw-r--r--core/java/android/service/notification/IConditionProvider.aidl28
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/values/strings.xml7
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java58
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java (renamed from services/core/java/com/android/server/notification/NotificationListeners.java)442
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java233
-rw-r--r--services/core/java/com/android/server/notification/NotificationUtil.java63
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java5
15 files changed, 780 insertions, 362 deletions
diff --git a/Android.mk b/Android.mk
index f89e7b2..df8fbd9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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>
+ * &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;
+ }
+ }
+ }
+}
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);
}