diff options
author | John Spurlock <jspurlock@google.com> | 2014-04-24 14:32:40 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-04-24 14:32:41 +0000 |
commit | 10459a541171357a18ee897ffcfed0150600fb16 (patch) | |
tree | 7cd5ef5f536036fdbe38d8fe6b5f82d4305d13e0 | |
parent | 6ec0b32173e6eeb643285975489ac95a08f6f315 (diff) | |
parent | b408e8ecd22853faa70e97d0596aac9e7dcf5596 (diff) | |
download | frameworks_base-10459a541171357a18ee897ffcfed0150600fb16.zip frameworks_base-10459a541171357a18ee897ffcfed0150600fb16.tar.gz frameworks_base-10459a541171357a18ee897ffcfed0150600fb16.tar.bz2 |
Merge "Move notification listener management into helper."
3 files changed, 749 insertions, 599 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationListeners.java b/services/core/java/com/android/server/notification/NotificationListeners.java new file mode 100644 index 0000000..91d2f98 --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationListeners.java @@ -0,0 +1,608 @@ +/** + * 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.PendingIntent; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +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; + + private static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":"; + + private final Context mContext; + private final Handler mHandler; + private final Object mMutex; + private final UserProfiles mUserProfiles; + private final SettingsObserver mSettingsObserver; + + // 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 + private final ArrayList<String> mServicesBinding = new ArrayList<String>(); + // lists the component names of all enabled (and therefore connected) listener + // app services for current profiles. + private ArraySet<ComponentName> mEnabledListenersForCurrentProfiles + = new ArraySet<ComponentName>(); + // Just the packages from mEnabledListenersForCurrentProfiles + private ArraySet<String> mEnabledListenerPackageNames = new ArraySet<String>(); + + public NotificationListeners(Context context, Handler handler, Object mutex, + UserProfiles userProfiles) { + mContext = context; + mHandler = handler; + mMutex = mutex; + mUserProfiles = userProfiles; + mSettingsObserver = new SettingsObserver(mHandler); + } + + public void onBootPhaseAppsCanStart() { + mSettingsObserver.observe(); + } + + protected void onServiceAdded(INotificationListener mListener) { + // for subclasses + } + + public void dump(PrintWriter pw) { + pw.println(" Listeners (" + mEnabledListenersForCurrentProfiles.size() + + ") enabled for current profiles:"); + for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) { + pw.println(" " + cmpt); + } + + pw.println(" Live listeners (" + mListeners.size() + "):"); + for (NotificationListenerInfo info : mListeners) { + pw.println(" " + info.component + + " (user " + info.userid + "): " + info.listener + + (info.isSystem?" SYSTEM":"")); + } + } + + public void onPackagesChanged(boolean queryReplace, String[] pkgList) { + boolean anyListenersInvolved = false; + if (pkgList != null && (pkgList.length > 0)) { + for (String pkgName : pkgList) { + if (mEnabledListenerPackageNames.contains(pkgName)) { + anyListenersInvolved = true; + } + } + } + + if (anyListenersInvolved) { + // if we're not replacing a package, clean up orphaned bits + if (!queryReplace) { + disableNonexistentListeners(); + } + // make sure we're still bound to any of our + // listeners who may have just upgraded + rebindListenerServices(); + } + } + + /** + * 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(); + for (int i=0; i<N; i++) { + final NotificationListenerInfo info = mListeners.get(i); + if (info.listener.asBinder() == token) return info; + } + throw new SecurityException("Disallowed call from unknown listener: " + listener); + } + + public void unregisterListener(INotificationListener listener, int userid) { + checkNullListener(listener); + // no need to check permissions; if your listener binder is in the list, + // that's proof that you had permission to add it in the first place + unregisterListenerImpl(listener, userid); + } + + public void registerListener(INotificationListener listener, + ComponentName component, int userid) { + checkNullListener(listener); + registerListenerImpl(listener, component, userid); + } + + /** + * Remove notification access for any services that no longer exist. + */ + private void disableNonexistentListeners() { + int[] userIds = mUserProfiles.getCurrentProfileIds(); + final int N = userIds.length; + for (int i = 0 ; i < N; ++i) { + disableNonexistentListeners(userIds[i]); + } + } + + private void disableNonexistentListeners(int userId) { + String flatIn = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + userId); + if (!TextUtils.isEmpty(flatIn)) { + if (DBG) Slog.v(TAG, "flat before: " + flatIn); + PackageManager pm = mContext.getPackageManager(); + List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( + new Intent(NotificationListenerService.SERVICE_INTERFACE), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, + userId); + + Set<ComponentName> installed = new ArraySet<ComponentName>(); + for (int i = 0, count = installedServices.size(); i < count; i++) { + 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 " + + info.packageName + "/" + info.name + + ": it does not require the permission " + + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); + continue; + } + installed.add(new ComponentName(info.packageName, info.name)); + } + + String flatOut = ""; + if (!installed.isEmpty()) { + String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + ArrayList<String> remaining = new ArrayList<String>(enabled.length); + for (int i = 0; i < enabled.length; i++) { + ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); + if (installed.contains(enabledComponent)) { + remaining.add(enabled[i]); + } + } + flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining); + } + if (DBG) Slog.v(TAG, "flat after: " + flatOut); + if (!flatIn.equals(flatOut)) { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + flatOut, userId); + } + } + } + + /** + * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS + * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) + */ + private void rebindListenerServices() { + final int[] userIds = mUserProfiles.getCurrentProfileIds(); + final int nUserIds = userIds.length; + + final SparseArray<String> flat = new SparseArray<String>(); + + for (int i = 0; i < nUserIds; ++i) { + flat.put(userIds[i], Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + userIds[i])); + } + + NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; + final SparseArray<ArrayList<ComponentName>> toAdd + = new SparseArray<ArrayList<ComponentName>>(); + + synchronized (mMutex) { + // unbind and remove all existing listeners + toRemove = mListeners.toArray(toRemove); + + final ArraySet<ComponentName> newEnabled = new ArraySet<ComponentName>(); + final ArraySet<String> newPackages = new ArraySet<String>(); + + for (int i = 0; i < nUserIds; ++i) { + final ArrayList<ComponentName> add = new ArrayList<ComponentName>(); + toAdd.put(userIds[i], add); + + // decode the list of components + String toDecode = flat.get(userIds[i]); + if (toDecode != null) { + String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); + for (int j = 0; j < components.length; j++) { + final ComponentName component + = ComponentName.unflattenFromString(components[j]); + if (component != null) { + newEnabled.add(component); + add.add(component); + newPackages.add(component.getPackageName()); + } + } + + } + } + mEnabledListenersForCurrentProfiles = newEnabled; + mEnabledListenerPackageNames = newPackages; + } + + for (NotificationListenerInfo info : toRemove) { + final ComponentName component = info.component; + final int oldUser = info.userid; + Slog.v(TAG, "disabling notification listener for user " + + oldUser + ": " + component); + unregisterListenerService(component, info.userid); + } + + for (int i = 0; i < nUserIds; ++i) { + final ArrayList<ComponentName> add = toAdd.get(userIds[i]); + 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] + ": " + + component); + registerListenerService(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. + */ + private void registerListenerService(final ComponentName name, final int userid) { + NotificationUtil.checkCallerIsSystem(); + + if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid); + + synchronized (mMutex) { + final String servicesBindingTag = name.toString() + "/" + userid; + if (mServicesBinding.contains(servicesBindingTag)) { + // stop registering this thing already! we're working on it + return; + } + mServicesBinding.add(servicesBindingTag); + + final int N = mListeners.size(); + for (int i=N-1; i>=0; i--) { + final NotificationListenerInfo info = mListeners.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 (info.connection != null) { + mContext.unbindService(info.connection); + } + } + } + + Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); + intent.setComponent(name); + + intent.putExtra(Intent.EXTRA_CLIENT_LABEL, + R.string.notification_listener_binding_label); + + final PendingIntent pendingIntent = PendingIntent.getActivity( + mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0); + intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); + + ApplicationInfo appInfo = null; + try { + appInfo = mContext.getPackageManager().getApplicationInfo( + name.getPackageName(), 0); + } catch (NameNotFoundException e) { + // Ignore if the package doesn't exist we won't be able to bind to the service. + } + final int targetSdkVersion = + appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE; + + try { + if (DBG) Slog.v(TAG, "binding: " + intent); + if (!mContext.bindServiceAsUser(intent, + new ServiceConnection() { + INotificationListener mListener; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + 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); + } catch (RemoteException e) { + // already dead + } + } + if (added) { + onServiceAdded(mListener); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Slog.v(TAG, "notification listener connection lost: " + name); + } + }, + Context.BIND_AUTO_CREATE, + new UserHandle(userid))) + { + mServicesBinding.remove(servicesBindingTag); + Slog.w(TAG, "Unable to bind listener service: " + intent); + return; + } + } catch (SecurityException ex) { + Slog.e(TAG, "Unable to bind listener service: " + intent, ex); + return; + } + } + } + + /** + * Remove a listener service for the given user by ComponentName + */ + private void unregisterListenerService(ComponentName name, int userid) { + NotificationUtil.checkCallerIsSystem(); + + synchronized (mMutex) { + final int N = mListeners.size(); + for (int i=N-1; i>=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (name.equals(info.component) + && info.userid == userid) { + mListeners.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); + } + } + } + } + } + } + + /** + * Removes a listener from the list but does not unbind from the listener's service. + * + * @return the removed listener. + */ + private NotificationListenerInfo removeListenerImpl( + final INotificationListener listener, final int userid) { + NotificationListenerInfo listenerInfo = null; + synchronized (mMutex) { + final int N = mListeners.size(); + for (int i=N-1; i>=0; i--) { + final NotificationListenerInfo info = mListeners.get(i); + if (info.listener.asBinder() == listener.asBinder() + && info.userid == userid) { + listenerInfo = mListeners.remove(i); + } + } + } + return listenerInfo; + } + + private void checkNullListener(INotificationListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Listener must not be null"); + } + } + + private void registerListenerImpl(final INotificationListener listener, + 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); + } catch (RemoteException e) { + // already dead + } + } + } + + /** + * Removes a listener from the list and unbinds from its service. + */ + private void unregisterListenerImpl(final INotificationListener listener, final int userid) { + NotificationListenerInfo info = removeListenerImpl(listener, 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 SettingsObserver(Handler handler) { + super(handler); + } + + private void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, + false, this, UserHandle.USER_ALL); + update(null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(uri); + } + + private void update(Uri uri) { + if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { + rebindListenerServices(); + } + } + } + + public class NotificationListenerInfo implements IBinder.DeathRecipient { + public INotificationListener listener; + 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; + 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(); + if (!isEnabledForCurrentProfiles()) { + return false; + } + if (this.userid == UserHandle.USER_ALL) return true; + if (nid == UserHandle.USER_ALL || nid == this.userid) return true; + return supportsProfiles() && mUserProfiles.isCurrentProfile(nid); + } + + public boolean supportsProfiles() { + 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 + // 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); + } + + /** convenience method for looking in mEnabledListenersForCurrentProfiles */ + public boolean isEnabledForCurrentProfiles() { + if (this.isSystem) return true; + if (this.connection == null) return false; + return mEnabledListenersForCurrentProfiles.contains(this.component); + } + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c8bdb4c..5f096cb 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -22,7 +22,6 @@ 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; @@ -36,12 +35,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; @@ -51,7 +46,6 @@ import android.media.AudioManager; import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.IBinder; @@ -63,7 +57,6 @@ import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; import android.service.notification.INotificationListener; -import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.telephony.TelephonyManager; @@ -82,11 +75,14 @@ import com.android.internal.R; import com.android.internal.notification.NotificationScorer; import com.android.internal.util.FastXmlSerializer; import com.android.server.EventLogTags; -import com.android.server.notification.NotificationUsageStats.SingleNotificationStats; -import com.android.server.statusbar.StatusBarManagerInternal; 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.NotificationUsageStats.SingleNotificationStats; +import com.android.server.statusbar.StatusBarManagerInternal; + +import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -107,9 +103,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import java.util.Set; - -import libcore.io.IoUtils; /** {@hide} */ public class NotificationManagerService extends SystemService { @@ -143,8 +136,6 @@ public class NotificationManagerService extends SystemService { static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true; static final boolean ENABLE_BLOCKED_TOASTS = true; - static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":"; - private IActivityManager mAm; AudioManager mAudioManager; StatusBarManagerInternal mStatusBar; @@ -185,19 +176,6 @@ public class NotificationManagerService extends SystemService { private AppOpsManager mAppOps; - // contains connections to all connected listeners, including app services - // and system listeners - private ArrayList<NotificationListenerInfo> mListeners - = new ArrayList<NotificationListenerInfo>(); - // things that will be put into mListeners as soon as they're ready - private ArrayList<String> mServicesBinding = new ArrayList<String>(); - // lists the component names of all enabled (and therefore connected) listener - // app services for current profiles. - private HashSet<ComponentName> mEnabledListenersForCurrentProfiles - = new HashSet<ComponentName>(); - // Just the packages from mEnabledListenersForCurrentProfiles - private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>(); - // Notification control database. For now just contains disabled packages. private AtomicFile mPolicyFile; private HashSet<String> mBlockedPackages = new HashSet<String>(); @@ -213,13 +191,13 @@ public class NotificationManagerService extends SystemService { final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>(); + private NotificationListeners mListeners; + private final UserProfiles mUserProfiles = new UserProfiles(); + private final NotificationUsageStats mUsageStats = new NotificationUsageStats(); private static final String EXTRA_INTERCEPT = "android.intercept"; - // Profiles of the current user. - final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); - private static final int MY_UID = Process.myUid(); private static final int MY_PID = Process.myPid(); private static final int REASON_DELEGATE_CLICK = 1; @@ -234,85 +212,6 @@ public class NotificationManagerService extends SystemService { private static final int REASON_LISTENER_CANCEL = 10; private static final int REASON_LISTENER_CANCEL_ALL = 11; - private class NotificationListenerInfo implements IBinder.DeathRecipient { - INotificationListener listener; - ComponentName component; - int userid; - boolean isSystem; - ServiceConnection connection; - int targetSdkVersion; - - public NotificationListenerInfo(INotificationListener listener, ComponentName component, - int userid, boolean isSystem, int targetSdkVersion) { - this.listener = listener; - 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; - } - - boolean enabledAndUserMatches(StatusBarNotification sbn) { - final int nid = sbn.getUserId(); - if (!isEnabledForCurrentProfiles()) { - return false; - } - if (this.userid == UserHandle.USER_ALL) return true; - if (nid == UserHandle.USER_ALL || nid == this.userid) return true; - return supportsProfiles() && isCurrentProfile(nid); - } - - boolean supportsProfiles() { - 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 - // 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); - } - - /** convenience method for looking in mEnabledListenersForCurrentProfiles */ - public boolean isEnabledForCurrentProfiles() { - if (this.isSystem) return true; - if (this.connection == null) return false; - return mEnabledListenersForCurrentProfiles.contains(this.component); - } - } - private static class Archive { static final int BUFFER_SIZE = 250; ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE); @@ -537,328 +436,6 @@ public class NotificationManagerService extends SystemService { } - /** - * Remove notification access for any services that no longer exist. - */ - void disableNonexistentListeners() { - int[] userIds = getCurrentProfileIds(); - final int N = userIds.length; - for (int i = 0 ; i < N; ++i) { - disableNonexistentListeners(userIds[i]); - } - } - - void disableNonexistentListeners(int userId) { - String flatIn = Settings.Secure.getStringForUser( - getContext().getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - userId); - if (!TextUtils.isEmpty(flatIn)) { - if (DBG) Slog.v(TAG, "flat before: " + flatIn); - PackageManager pm = getContext().getPackageManager(); - List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser( - new Intent(NotificationListenerService.SERVICE_INTERFACE), - PackageManager.GET_SERVICES | PackageManager.GET_META_DATA, - userId); - - Set<ComponentName> installed = new HashSet<ComponentName>(); - for (int i = 0, count = installedServices.size(); i < count; i++) { - 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 " - + info.packageName + "/" + info.name - + ": it does not require the permission " - + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE); - continue; - } - installed.add(new ComponentName(info.packageName, info.name)); - } - - String flatOut = ""; - if (!installed.isEmpty()) { - String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - ArrayList<String> remaining = new ArrayList<String>(enabled.length); - for (int i = 0; i < enabled.length; i++) { - ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]); - if (installed.contains(enabledComponent)) { - remaining.add(enabled[i]); - } - } - flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining); - } - if (DBG) Slog.v(TAG, "flat after: " + flatOut); - if (!flatIn.equals(flatOut)) { - Settings.Secure.putStringForUser(getContext().getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - flatOut, userId); - } - } - } - - /** - * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS - * is altered. (For example in response to USER_SWITCHED in our broadcast receiver) - */ - void rebindListenerServices() { - final int[] userIds = getCurrentProfileIds(); - final int nUserIds = userIds.length; - - final SparseArray<String> flat = new SparseArray<String>(); - - for (int i = 0; i < nUserIds; ++i) { - flat.put(userIds[i], Settings.Secure.getStringForUser( - getContext().getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - userIds[i])); - } - - NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()]; - final SparseArray<ArrayList<ComponentName>> toAdd - = new SparseArray<ArrayList<ComponentName>>(); - - synchronized (mNotificationList) { - // unbind and remove all existing listeners - toRemove = mListeners.toArray(toRemove); - - final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>(); - final HashSet<String> newPackages = new HashSet<String>(); - - for (int i = 0; i < nUserIds; ++i) { - final ArrayList<ComponentName> add = new ArrayList<ComponentName>(); - toAdd.put(userIds[i], add); - - // decode the list of components - String toDecode = flat.get(userIds[i]); - if (toDecode != null) { - String[] components = toDecode.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR); - for (int j = 0; j < components.length; j++) { - final ComponentName component - = ComponentName.unflattenFromString(components[j]); - if (component != null) { - newEnabled.add(component); - add.add(component); - newPackages.add(component.getPackageName()); - } - } - - } - } - mEnabledListenersForCurrentProfiles = newEnabled; - mEnabledListenerPackageNames = newPackages; - } - - for (NotificationListenerInfo info : toRemove) { - final ComponentName component = info.component; - final int oldUser = info.userid; - Slog.v(TAG, "disabling notification listener for user " - + oldUser + ": " + component); - unregisterListenerService(component, info.userid); - } - - for (int i = 0; i < nUserIds; ++i) { - final ArrayList<ComponentName> add = toAdd.get(userIds[i]); - 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] + ": " - + component); - registerListenerService(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. - */ - private void registerListenerService(final ComponentName name, final int userid) { - checkCallerIsSystem(); - - if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid); - - synchronized (mNotificationList) { - final String servicesBindingTag = name.toString() + "/" + userid; - if (mServicesBinding.contains(servicesBindingTag)) { - // stop registering this thing already! we're working on it - return; - } - mServicesBinding.add(servicesBindingTag); - - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.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 (info.connection != null) { - getContext().unbindService(info.connection); - } - } - } - - Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE); - intent.setComponent(name); - - intent.putExtra(Intent.EXTRA_CLIENT_LABEL, - R.string.notification_listener_binding_label); - - final PendingIntent pendingIntent = PendingIntent.getActivity( - getContext(), 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0); - intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent); - - ApplicationInfo appInfo = null; - try { - appInfo = getContext().getPackageManager().getApplicationInfo( - name.getPackageName(), 0); - } catch (NameNotFoundException e) { - // Ignore if the package doesn't exist we won't be able to bind to the service. - } - final int targetSdkVersion = - appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE; - - try { - if (DBG) Slog.v(TAG, "binding: " + intent); - if (!getContext().bindServiceAsUser(intent, - new ServiceConnection() { - INotificationListener mListener; - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - boolean added = false; - synchronized (mNotificationList) { - 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); - } catch (RemoteException e) { - // already dead - } - } - if (added) { - final String[] keys = - getActiveNotificationKeysFromListener(mListener); - try { - mListener.onListenerConnected(keys); - } catch (RemoteException e) { - // we tried - } - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Slog.v(TAG, "notification listener connection lost: " + name); - } - }, - Context.BIND_AUTO_CREATE, - new UserHandle(userid))) - { - mServicesBinding.remove(servicesBindingTag); - Slog.w(TAG, "Unable to bind listener service: " + intent); - return; - } - } catch (SecurityException ex) { - Slog.e(TAG, "Unable to bind listener service: " + intent, ex); - return; - } - } - } - - - /** - * Remove a listener service for the given user by ComponentName - */ - private void unregisterListenerService(ComponentName name, int userid) { - checkCallerIsSystem(); - - synchronized (mNotificationList) { - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (name.equals(info.component) - && info.userid == userid) { - mListeners.remove(i); - if (info.connection != null) { - try { - getContext().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); - } - } - } - } - } - } - - /** - * asynchronously notify all listeners about a new notification - */ - void notifyPostedLocked(NotificationRecord n) { - // make a copy in case changes are made to the underlying Notification object - final StatusBarNotification sbn = n.sbn.clone(); - for (final NotificationListenerInfo info : mListeners) { - mHandler.post(new Runnable() { - @Override - public void run() { - info.notifyPostedIfUserMatch(sbn); - }}); - } - } - - /** - * asynchronously notify all listeners about a removed notification - */ - void notifyRemovedLocked(NotificationRecord n) { - // 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 sbn_light = n.sbn.cloneLight(); - - for (final NotificationListenerInfo info : mListeners) { - mHandler.post(new Runnable() { - @Override - public void run() { - info.notifyRemovedIfUserMatch(sbn_light); - }}); - } - } - - // -- APIs to support listeners clicking/clearing notifications -- - - private void checkNullListener(INotificationListener listener) { - if (listener == null) { - throw new IllegalArgumentException("Listener must not be null"); - } - } - - private NotificationListenerInfo checkListenerTokenLocked(INotificationListener listener) { - checkNullListener(listener); - final IBinder token = listener.asBinder(); - final int N = mListeners.size(); - for (int i=0; i<N; i++) { - final NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == token) return info; - } - throw new SecurityException("Disallowed call from unknown listener: " + listener); - } - - - - // -- end of listener APIs -- public static final class NotificationRecord { @@ -1159,28 +736,15 @@ public class NotificationManagerService extends SystemService { pkgList = new String[]{pkgName}; } - boolean anyListenersInvolved = false; if (pkgList != null && (pkgList.length > 0)) { for (String pkgName : pkgList) { if (cancelNotifications) { cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart, UserHandle.USER_ALL, REASON_PACKAGE_CHANGED, null); } - if (mEnabledListenerPackageNames.contains(pkgName)) { - anyListenersInvolved = true; - } - } - } - - if (anyListenersInvolved) { - // if we're not replacing a package, clean up orphaned bits - if (!queryReplace) { - disableNonexistentListeners(); } - // make sure we're still bound to any of our - // listeners who may have just upgraded - rebindListenerServices(); } + mListeners.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. @@ -1203,9 +767,9 @@ public class NotificationManagerService extends SystemService { } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { // reload per-user settings mSettingsObserver.update(null); - updateCurrentProfilesCache(context); + mUserProfiles.updateCache(context); } else if (action.equals(Intent.ACTION_USER_ADDED)) { - updateCurrentProfilesCache(context); + mUserProfiles.updateCache(context); } } }; @@ -1214,9 +778,6 @@ public class NotificationManagerService extends SystemService { private final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE); - private final Uri ENABLED_NOTIFICATION_LISTENERS_URI - = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - SettingsObserver(Handler handler) { super(handler); } @@ -1225,8 +786,6 @@ public class NotificationManagerService extends SystemService { ContentResolver resolver = getContext().getContentResolver(); resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, - false, this, UserHandle.USER_ALL); update(null); } @@ -1244,9 +803,6 @@ public class NotificationManagerService extends SystemService { updateNotificationPulse(); } } - if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { - rebindListenerServices(); - } } } @@ -1289,6 +845,18 @@ 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 + } + } + }; mStatusBar = getLocalService(StatusBarManagerInternal.class); mStatusBar.setNotificationDelegate(mNotificationDelegate); @@ -1324,7 +892,7 @@ public class NotificationManagerService extends SystemService { } mZenModeHelper.updateZenMode(); - updateCurrentProfilesCache(getContext()); + mUserProfiles.updateCache(getContext()); // register for various Intents IntentFilter filter = new IntentFilter(); @@ -1403,6 +971,7 @@ public class NotificationManagerService extends SystemService { // This observer will force an update when observe is called, causing us to // bind to listener services. mSettingsObserver.observe(); + mListeners.onBootPhaseAppsCanStart(); } } @@ -1436,7 +1005,8 @@ public class NotificationManagerService extends SystemService { return ; } - final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg)); + final boolean isSystemToast = + NotificationUtil.isCallerSystem() || ("android".equals(pkg)); if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) { if (!isSystemToast) { @@ -1527,7 +1097,7 @@ public class NotificationManagerService extends SystemService { @Override public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) { - checkCallerIsSystemOrSameApp(pkg); + NotificationUtil.checkCallerIsSystemOrSameApp(pkg); userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg); // Don't allow client applications to cancel foreground service notis. @@ -1539,7 +1109,7 @@ public class NotificationManagerService extends SystemService { @Override public void cancelAllNotifications(String pkg, int userId) { - checkCallerIsSystemOrSameApp(pkg); + NotificationUtil.checkCallerIsSystemOrSameApp(pkg); userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); @@ -1553,7 +1123,7 @@ public class NotificationManagerService extends SystemService { @Override public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) { - checkCallerIsSystem(); + NotificationUtil.checkCallerIsSystem(); setNotificationsEnabledForPackageImpl(pkg, uid, enabled); } @@ -1563,7 +1133,7 @@ public class NotificationManagerService extends SystemService { */ @Override public boolean areNotificationsEnabledForPackage(String pkg, int uid) { - checkCallerIsSystem(); + NotificationUtil.checkCallerIsSystem(); return (mAppOps.checkOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg) == AppOpsManager.MODE_ALLOWED); } @@ -1631,9 +1201,8 @@ public class NotificationManagerService extends SystemService { @Override public void registerListener(final INotificationListener listener, final ComponentName component, final int userid) { - checkCallerIsSystem(); - checkNullListener(listener); - registerListenerImpl(listener, component, userid); + NotificationUtil.checkCallerIsSystem(); + mListeners.registerListener(listener, component, userid); } /** @@ -1641,10 +1210,7 @@ public class NotificationManagerService extends SystemService { */ @Override public void unregisterListener(INotificationListener listener, int userid) { - checkNullListener(listener); - // no need to check permissions; if your listener binder is in the list, - // that's proof that you had permission to add it in the first place - unregisterListenerImpl(listener, userid); + mListeners.unregisterListener(listener, userid); } /** @@ -1661,14 +1227,15 @@ public class NotificationManagerService extends SystemService { long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationList) { - final NotificationListenerInfo info = checkListenerTokenLocked(token); + final NotificationListenerInfo info = + mListeners.checkListenerTokenLocked(token); if (keys != null) { final int N = keys.length; for (int i = 0; i < N; i++) { NotificationRecord r = mNotificationsByKey.get(keys[i]); final int userId = r.sbn.getUserId(); if (userId != info.userid && userId != UserHandle.USER_ALL && - !isCurrentProfile(userId)) { + !mUserProfiles.isCurrentProfile(userId)) { throw new SecurityException("Disallowed call from listener: " + info.listener); } @@ -1711,7 +1278,8 @@ public class NotificationManagerService extends SystemService { long identity = Binder.clearCallingIdentity(); try { synchronized (mNotificationList) { - final NotificationListenerInfo info = checkListenerTokenLocked(token); + final NotificationListenerInfo info = + mListeners.checkListenerTokenLocked(token); if (info.supportsProfiles()) { Log.e(TAG, "Ignoring deprecated cancelNotification(pkg, tag, id) " + "from " + info.component @@ -1737,7 +1305,7 @@ public class NotificationManagerService extends SystemService { public StatusBarNotification[] getActiveNotificationsFromListener( INotificationListener token, String[] keys) { synchronized (mNotificationList) { - final NotificationListenerInfo info = checkListenerTokenLocked(token); + final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token); final ArrayList<StatusBarNotification> list = new ArrayList<StatusBarNotification>(); if (keys == null) { @@ -1768,13 +1336,13 @@ public class NotificationManagerService extends SystemService { @Override public ZenModeConfig getZenModeConfig() { - checkCallerIsSystem(); + NotificationUtil.checkCallerIsSystem(); return mZenModeHelper.getConfig(); } @Override public boolean setZenModeConfig(ZenModeConfig config) { - checkCallerIsSystem(); + NotificationUtil.checkCallerIsSystem(); return mZenModeHelper.setConfig(config); } @@ -1794,7 +1362,7 @@ public class NotificationManagerService extends SystemService { private String[] getActiveNotificationKeysFromListener(INotificationListener token) { synchronized (mNotificationList) { - final NotificationListenerInfo info = checkListenerTokenLocked(token); + final NotificationListenerInfo info = mListeners.checkListenerTokenLocked(token); final ArrayList<String> keys = new ArrayList<String>(); final int N = mNotificationList.size(); for (int i=0; i<N; i++) { @@ -1810,18 +1378,7 @@ public class NotificationManagerService extends SystemService { void dumpImpl(PrintWriter pw) { pw.println("Current Notification Manager state:"); - pw.println(" Listeners (" + mEnabledListenersForCurrentProfiles.size() - + ") enabled for current profiles:"); - for (ComponentName cmpt : mEnabledListenersForCurrentProfiles) { - pw.println(" " + cmpt); - } - - pw.println(" Live listeners (" + mListeners.size() + "):"); - for (NotificationListenerInfo info : mListeners) { - pw.println(" " + info.component - + " (user " + info.userid + "): " + info.listener - + (info.isSystem?" SYSTEM":"")); - } + mListeners.dump(pw); int N; @@ -1898,8 +1455,9 @@ public class NotificationManagerService extends SystemService { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); } - checkCallerIsSystemOrSameApp(pkg); - final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); + NotificationUtil.checkCallerIsSystemOrSameApp(pkg); + final boolean isSystemNotification = + NotificationUtil.isUidSystem(callingUid) || ("android".equals(pkg)); final int userId = ActivityManager.handleIncomingUser(callingPid, callingUid, incomingUserId, true, false, "enqueueNotification", pkg); @@ -2081,7 +1639,7 @@ public class NotificationManagerService extends SystemService { sendAccessibilityEvent(notification, pkg); } - notifyPostedLocked(r); + mListeners.notifyPostedLocked(r.sbn); } else { Slog.e(TAG, "Not posting notification with icon==0: " + notification); if (old != null && old.statusBarKey != null) { @@ -2092,7 +1650,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } - notifyRemovedLocked(r); + mListeners.notifyRemovedLocked(r.sbn); } // ATTENTION: in a future release we will bail out here // so that we do not play sounds, show lights, etc. for invalid @@ -2107,7 +1665,7 @@ public class NotificationManagerService extends SystemService { && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) && (r.getUserId() == UserHandle.USER_ALL || (r.getUserId() == userId && r.getUserId() == currentUser) || - isCurrentProfile(r.getUserId())) + mUserProfiles.isCurrentProfile(r.getUserId())) && canInterrupt && mSystemReady && mAudioManager != null) { @@ -2238,52 +1796,6 @@ public class NotificationManagerService extends SystemService { idOut[0] = id; } - void registerListenerImpl(final INotificationListener listener, - final ComponentName component, final int userid) { - synchronized (mNotificationList) { - try { - NotificationListenerInfo info - = new NotificationListenerInfo(listener, component, userid, - /*isSystem*/ true, Build.VERSION_CODES.L); - listener.asBinder().linkToDeath(info, 0); - mListeners.add(info); - } catch (RemoteException e) { - // already dead - } - } - } - - /** - * Removes a listener from the list and unbinds from its service. - */ - void unregisterListenerImpl(final INotificationListener listener, final int userid) { - NotificationListenerInfo info = removeListenerImpl(listener, userid); - if (info != null && info.connection != null) { - getContext().unbindService(info.connection); - } - } - - /** - * Removes a listener from the list but does not unbind from the listener's service. - * - * @return the removed listener. - */ - NotificationListenerInfo removeListenerImpl( - final INotificationListener listener, final int userid) { - NotificationListenerInfo listenerInfo = null; - synchronized (mNotificationList) { - final int N = mListeners.size(); - for (int i=N-1; i>=0; i--) { - final NotificationListenerInfo info = mListeners.get(i); - if (info.listener.asBinder() == listener.asBinder() - && info.userid == userid) { - listenerInfo = mListeners.remove(i); - } - } - } - return listenerInfo; - } - void showNextToastLocked() { ToastRecord record = mToastQueue.get(0); while (record != null) { @@ -2449,7 +1961,7 @@ public class NotificationManagerService extends SystemService { Binder.restoreCallingIdentity(identity); } r.statusBarKey = null; - notifyRemovedLocked(r); + mListeners.notifyRemovedLocked(r.sbn); } // sound @@ -2578,7 +2090,7 @@ public class NotificationManagerService extends SystemService { */ private boolean notificationMatchesCurrentProfiles(NotificationRecord r, int userId) { return notificationMatchesUserId(r, userId) - || isCurrentProfile(r.getUserId()); + || mUserProfiles.isCurrentProfile(r.getUserId()); } /** @@ -2628,44 +2140,6 @@ public class NotificationManagerService extends SystemService { } } - - - // Return true if the UID is a system or phone UID and therefore should not have - // any notifications or toasts blocked. - 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. - boolean isCallerSystem() { - return isUidSystem(Binder.getCallingUid()); - } - - void checkCallerIsSystem() { - if (isCallerSystem()) { - return; - } - throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid()); - } - - 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); - } - } - void cancelAllLocked(int callingUid, int callingPid, int userId, int reason, NotificationListenerInfo listener, boolean includeCurrentProfiles) { EventLogTags.writeNotificationCancelAll(callingUid, callingPid, @@ -2760,34 +2234,39 @@ public class NotificationManagerService extends SystemService { } } - private void updateCurrentProfilesCache(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 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 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); + 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; } - return users; } - } - private boolean isCurrentProfile(int userId) { - synchronized (mCurrentProfiles) { - return mCurrentProfiles.get(userId) != null; + public boolean isCurrentProfile(int userId) { + synchronized (mCurrentProfiles) { + return mCurrentProfiles.get(userId) != null; + } } } } diff --git a/services/core/java/com/android/server/notification/NotificationUtil.java b/services/core/java/com/android/server/notification/NotificationUtil.java new file mode 100644 index 0000000..459adce --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationUtil.java @@ -0,0 +1,63 @@ +/** + * 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); + } + } +} |