implement assistant service connection

Added a guest-mode to ManagedServices.
Like system services, the lifecycle of a guest is not managed.
Unlike system services, guests are not considered privledged.

The Assistant gets all the usual listener events.
Implemented adjustImportance.
Future work: enqueued, clicked, visibility, removed, annotations

Bug: 22455414
Change-Id: Ic41c0bf625b5e98cb577b49098bba23a539bb507
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 1987214..ce18818 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -121,6 +121,11 @@
     }
 
     @Override
+    protected boolean checkType(IInterface service) {
+        return service instanceof IConditionProvider;
+    }
+
+    @Override
     public void onBootPhaseAppsCanStart() {
         super.onBootPhaseAppsCanStart();
         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index b7662da..09e6647 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -141,6 +141,8 @@
 
     abstract protected IInterface asInterface(IBinder binder);
 
+    abstract protected boolean checkType(IInterface service);
+
     abstract protected void onServiceAdded(ManagedServiceInfo info);
 
     protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
@@ -169,7 +171,8 @@
             if (filter != null && !filter.matches(info.component)) continue;
             pw.println("      " + info.component
                     + " (user " + info.userid + "): " + info.service
-                    + (info.isSystem?" SYSTEM":""));
+                    + (info.isSystem?" SYSTEM":"")
+                    + (info.isGuest(this)?" GUEST":""));
         }
     }
 
@@ -266,6 +269,18 @@
         }
     }
 
+    /**
+     * Add a service to our callbacks. The lifecycle of this service is managed externally,
+     * but unlike a system service, it should not be considered privledged.
+     * */
+    public void registerGuestService(ManagedServiceInfo guest) {
+        checkNotNull(guest.service);
+        checkType(guest.service);
+        if (registerServiceImpl(guest) != null) {
+            onServiceAdded(guest);
+        }
+    }
+
     public void setCategoryState(String category, boolean enabled) {
         synchronized (mMutex) {
             final Boolean previous = mCategoryEnabled.put(category, enabled);
@@ -484,7 +499,7 @@
         synchronized (mMutex) {
             // Unbind automatically bound services, retain system services.
             for (ManagedServiceInfo service : mServices) {
-                if (!service.isSystem) {
+                if (!service.isSystem && !service.isGuest(this)) {
                     toRemove.add(service);
                 }
             }
@@ -709,11 +724,15 @@
 
     private ManagedServiceInfo registerServiceImpl(final IInterface service,
             final ComponentName component, final int userid) {
+        ManagedServiceInfo info = newServiceInfo(service, component, userid,
+                true /*isSystem*/, null /*connection*/, Build.VERSION_CODES.LOLLIPOP);
+        return registerServiceImpl(info);
+    }
+
+    private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) {
         synchronized (mMutex) {
             try {
-                ManagedServiceInfo info = newServiceInfo(service, component, userid,
-                        true /*isSystem*/, null, Build.VERSION_CODES.LOLLIPOP);
-                service.asBinder().linkToDeath(info, 0);
+                info.service.asBinder().linkToDeath(info, 0);
                 mServices.add(info);
                 return info;
             } catch (RemoteException e) {
@@ -728,7 +747,7 @@
      */
     private void unregisterServiceImpl(IInterface service, int userid) {
         ManagedServiceInfo info = removeServiceImpl(service, userid);
-        if (info != null && info.connection != null) {
+        if (info != null && info.connection != null && !info.isGuest(this)) {
             mContext.unbindService(info.connection);
         }
     }
@@ -780,6 +799,10 @@
             this.targetSdkVersion = targetSdkVersion;
         }
 
+        public boolean isGuest(ManagedServices host) {
+            return ManagedServices.this != host;
+        }
+
         @Override
         public String toString() {
             return new StringBuilder("ManagedServiceInfo[")
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 49aa73e..82c38af 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -39,6 +39,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
@@ -96,6 +97,7 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
+import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.StatusBarNotification;
@@ -154,6 +156,7 @@
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /** {@hide} */
 public class NotificationManagerService extends SystemService {
@@ -167,11 +170,13 @@
     // message codes
     static final int MESSAGE_TIMEOUT = 2;
     static final int MESSAGE_SAVE_POLICY_FILE = 3;
-    static final int MESSAGE_RECONSIDER_RANKING = 4;
-    static final int MESSAGE_RANKING_CONFIG_CHANGE = 5;
-    static final int MESSAGE_SEND_RANKING_UPDATE = 6;
-    static final int MESSAGE_LISTENER_HINTS_CHANGED = 7;
-    static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 8;
+    static final int MESSAGE_SEND_RANKING_UPDATE = 4;
+    static final int MESSAGE_LISTENER_HINTS_CHANGED = 5;
+    static final int MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED = 6;
+
+    // ranking thread messages
+    private static final int MESSAGE_RECONSIDER_RANKING = 1000;
+    private static final int MESSAGE_RANKING_SORT = 1001;
 
     static final int LONG_DELAY = 3500; // 3.5 seconds
     static final int SHORT_DELAY = 2000; // 2 seconds
@@ -281,11 +286,13 @@
 
     private final UserProfiles mUserProfiles = new UserProfiles();
     private NotificationListeners mListeners;
+    private NotificationAssistant mAssistant;
     private ConditionProviders mConditionProviders;
     private NotificationUsageStats mUsageStats;
 
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
+    private RankingHandler mRankingHandler;
 
     private static class Archive {
         final int mBufferSize;
@@ -738,6 +745,7 @@
                     }
                 }
                 mListeners.onPackagesChanged(queryReplace, pkgList);
+                mAssistant.onPackagesChanged(queryReplace, pkgList);
                 mConditionProviders.onPackagesChanged(queryReplace, pkgList);
                 mRankingHelper.onPackagesChanged(queryReplace, pkgList);
             }
@@ -779,6 +787,7 @@
                 // Refresh managed services
                 mConditionProviders.onUserSwitched(user);
                 mListeners.onUserSwitched(user);
+                mAssistant.onUserSwitched(user);
                 mZenModeHelper.onUserSwitched(user);
             } else if (action.equals(Intent.ACTION_USER_ADDED)) {
                 mUserProfiles.updateCache(context);
@@ -874,8 +883,9 @@
             extractorNames = new String[0];
         }
         mUsageStats = new NotificationUsageStats(getContext());
+        mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
         mRankingHelper = new RankingHelper(getContext(),
-                new RankingWorkerHandler(mRankingThread.getLooper()),
+                mRankingHandler,
                 mUsageStats,
                 extractorNames);
         mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
@@ -909,6 +919,7 @@
         importOldBlockDb();
 
         mListeners = new NotificationListeners();
+        mAssistant = new NotificationAssistant();
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         mStatusBar.setNotificationDelegate(mNotificationDelegate);
 
@@ -1025,6 +1036,7 @@
             // bind to listener services.
             mSettingsObserver.observe();
             mListeners.onBootPhaseAppsCanStart();
+            mAssistant.onBootPhaseAppsCanStart();
             mConditionProviders.onBootPhaseAppsCanStart();
         }
     }
@@ -1896,6 +1908,22 @@
                 Binder.restoreCallingIdentity(identity);
             }
         }
+
+        @Override
+        public void setImportanceFromAssistant(INotificationListener token, String key,
+                int importance, CharSequence explanation) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mNotificationList) {
+                    mAssistant.checkServiceTokenLocked(token);
+                    NotificationRecord n = mNotificationsByKey.get(key);
+                    n.setImportance(importance, explanation);
+                    mRankingHandler.requestSort();
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     };
 
     private String disableNotificationEffects(NotificationRecord record) {
@@ -2037,6 +2065,8 @@
                     pw.print(listener.component);
                 }
                 pw.println(')');
+                pw.println("\n  Notification assistant:");
+                mAssistant.dump(pw, filter);
             }
             pw.println("\n  Policy access:");
             pw.print("    mPolicyAccess: "); pw.println(mPolicyAccess);
@@ -2686,7 +2716,7 @@
         }
     }
 
-    private void handleRankingConfigChange() {
+    private void handleRankingSort() {
         synchronized (mNotificationList) {
             final int N = mNotificationList.size();
             ArrayList<String> orderBefore = new ArrayList<String>(N);
@@ -2788,9 +2818,9 @@
 
     }
 
-    private final class RankingWorkerHandler extends Handler
+    private final class RankingHandlerWorker extends Handler implements RankingHandler
     {
-        public RankingWorkerHandler(Looper looper) {
+        public RankingHandlerWorker(Looper looper) {
             super(looper);
         }
 
@@ -2800,11 +2830,23 @@
                 case MESSAGE_RECONSIDER_RANKING:
                     handleRankingReconsideration(msg);
                     break;
-                case MESSAGE_RANKING_CONFIG_CHANGE:
-                    handleRankingConfigChange();
+                case MESSAGE_RANKING_SORT:
+                    handleRankingSort();
                     break;
             }
         }
+
+        public void requestSort() {
+            removeMessages(MESSAGE_RANKING_SORT);
+            sendEmptyMessage(MESSAGE_RANKING_SORT);
+        }
+
+        public void requestReconsideration(RankingReconsideration recon) {
+            Message m = Message.obtain(this,
+                    NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon);
+            long delay = recon.getDelay(TimeUnit.MILLISECONDS);
+            sendMessageDelayed(m, delay);
+        }
     }
 
     // Notifications
@@ -3305,6 +3347,45 @@
         return true;
     }
 
+    public class NotificationAssistant extends ManagedServices {
+
+        public NotificationAssistant() {
+            super(getContext(), mHandler, mNotificationList, mUserProfiles);
+        }
+
+        @Override
+        protected Config getConfig() {
+            Config c = new Config();
+            c.caption = "notification assistant";
+            c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
+            c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
+            c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
+            c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
+            c.clientLabel = R.string.notification_assistant_binding_label;
+            return c;
+        }
+
+        @Override
+        protected IInterface asInterface(IBinder binder) {
+            return INotificationListener.Stub.asInterface(binder);
+        }
+
+        @Override
+        protected boolean checkType(IInterface service) {
+            return service instanceof INotificationListener;
+        }
+
+        @Override
+        protected void onServiceAdded(ManagedServiceInfo info) {
+            mListeners.registerGuestService(info);
+        }
+
+        @Override
+        protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
+            mListeners.unregisterService(removed.service, removed.userid);
+        }
+    }
+
     public class NotificationListeners extends ManagedServices {
 
         private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
@@ -3332,6 +3413,11 @@
         }
 
         @Override
+        protected boolean checkType(IInterface service) {
+            return service instanceof INotificationListener;
+        }
+
+        @Override
         public void onServiceAdded(ManagedServiceInfo info) {
             final INotificationListener listener = (INotificationListener) info.service;
             final NotificationRankingUpdate update;
@@ -3366,7 +3452,6 @@
 
         public int getOnNotificationPostedTrim(ManagedServiceInfo info) {
             return mLightTrimListeners.contains(info) ? TRIM_LIGHT : TRIM_FULL;
-
         }
 
         /**
diff --git a/services/core/java/com/android/server/notification/RankingHandler.java b/services/core/java/com/android/server/notification/RankingHandler.java
new file mode 100644
index 0000000..80bb4f0
--- /dev/null
+++ b/services/core/java/com/android/server/notification/RankingHandler.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2015, 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;
+
+public interface RankingHandler {
+    public void requestSort();
+    public void requestReconsideration(RankingReconsideration recon);
+}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 3287f67..0662e7a 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -19,10 +19,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Handler;
-import android.os.Message;
 import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -40,7 +37,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 public class RankingHelper implements RankingConfig {
     private static final String TAG = "RankingHelper";
@@ -73,10 +69,10 @@
     private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
 
     private final Context mContext;
-    private final Handler mRankingHandler;
+    private final RankingHandler mRankingHandler;
 
-    public RankingHelper(Context context, Handler rankingHandler, NotificationUsageStats usageStats,
-            String[] extractorNames) {
+    public RankingHelper(Context context, RankingHandler rankingHandler,
+            NotificationUsageStats usageStats, String[] extractorNames) {
         mContext = context;
         mRankingHandler = rankingHandler;
 
@@ -119,10 +115,7 @@
             try {
                 RankingReconsideration recon = extractor.process(r);
                 if (recon != null) {
-                    Message m = Message.obtain(mRankingHandler,
-                            NotificationManagerService.MESSAGE_RECONSIDER_RANKING, recon);
-                    long delay = recon.getDelay(TimeUnit.MILLISECONDS);
-                    mRankingHandler.sendMessageDelayed(m, delay);
+                    mRankingHandler.requestReconsideration(recon);
                 }
             } catch (Throwable t) {
                 Slog.w(TAG, "NotificationSignalExtractor failed.", t);
@@ -287,7 +280,7 @@
         for (int i = 0; i < N; i++) {
             mSignalExtractors[i].setConfig(this);
         }
-        mRankingHandler.sendEmptyMessage(NotificationManagerService.MESSAGE_RANKING_CONFIG_CHANGE);
+        mRankingHandler.requestSort();
     }
 
     public void sort(ArrayList<NotificationRecord> notificationList) {
diff --git a/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java
index df7b412..2640889 100644
--- a/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -35,7 +35,7 @@
 
 public class RankingHelperTest extends AndroidTestCase {
     @Mock NotificationUsageStats mUsageStats;
-    @Mock Handler handler;
+    @Mock RankingHandler handler;
 
     private Notification mNotiGroupGSortA;
     private Notification mNotiGroupGSortB;