Merge "One time permission grant to default noti assistant"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e3a910f..3b02a96 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3249,4 +3249,7 @@
     <string name="config_fontFamilyButton">@string/font_family_button_material</string>
 
     <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
+
+    <!-- Package name that should be granted Notification Assistant access -->
+    <string name="config_defaultAssistantAccessPackage" translatable="false">android.ext.services</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 50dc384..638f1b2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3216,4 +3216,6 @@
   <java-symbol type="string" name="harmful_app_warning_uninstall" />
   <java-symbol type="string" name="harmful_app_warning_launch_anyway" />
   <java-symbol type="string" name="harmful_app_warning_title" />
+
+  <java-symbol type="string" name="config_defaultAssistantAccessPackage" />
 </resources>
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 37e6ae9..42093e8 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -71,6 +71,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -98,6 +99,9 @@
     static final String ATT_APPROVED_LIST = "approved";
     static final String ATT_USER_ID = "user";
     static final String ATT_IS_PRIMARY = "primary";
+    static final String ATT_VERSION = "version";
+
+    static final int DB_VERSION = 1;
 
     static final int APPROVAL_BY_PACKAGE = 0;
     static final int APPROVAL_BY_COMPONENT = 1;
@@ -295,6 +299,8 @@
     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
         out.startTag(null, getConfig().xmlTag);
 
+        out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
+
         if (forBackup) {
             trimApprovedListsAccordingToInstalledServices();
         }
@@ -336,6 +342,14 @@
 
     public void readXml(XmlPullParser parser)
             throws XmlPullParserException, IOException {
+        // upgrade xml
+        int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
+        final List<UserInfo> activeUsers = mUm.getUsers(true);
+        for (UserInfo userInfo : activeUsers) {
+            upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier());
+        }
+
+        // read grants
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             String tag = parser.getName();
@@ -346,6 +360,7 @@
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_MANAGED_SERVICES.equals(tag)) {
                     Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml");
+
                     final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST);
                     final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
                     final boolean isPrimary =
@@ -360,6 +375,8 @@
         rebindServices(false);
     }
 
+    protected void upgradeXml(final int xmlVersion, final int userId) {}
+
     private void loadAllowedComponentsFromSettings() {
 
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
@@ -379,7 +396,7 @@
         Slog.d(TAG, "Done loading approved values from settings");
     }
 
-    private void addApprovedList(String approved, int userId, boolean isPrimary) {
+    protected void addApprovedList(String approved, int userId, boolean isPrimary) {
         if (TextUtils.isEmpty(approved)) {
             approved = "";
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 16b9257..2f6618f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -434,6 +434,7 @@
                 }
             }
         }
+
         String defaultDndAccess = getContext().getResources().getString(
                 com.android.internal.R.string.config_defaultDndAccessPackages);
         if (defaultListenerAccess != null) {
@@ -446,6 +447,29 @@
                 }
             }
         }
+
+        readDefaultAssistant(userId);
+    }
+
+    protected void readDefaultAssistant(int userId) {
+        String defaultAssistantAccess = getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultAssistantAccessPackage);
+        if (defaultAssistantAccess != null) {
+            // Gather all notification assistant components for candidate pkg. There should
+            // only be one
+            Set<ComponentName> approvedAssistants =
+                    mAssistants.queryPackageForServices(defaultAssistantAccess,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+            for (ComponentName cn : approvedAssistants) {
+                try {
+                    getBinderService().setNotificationAssistantAccessGrantedForUser(cn,
+                            userId, true);
+                } catch (RemoteException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
     }
 
     void readPolicyXml(InputStream stream, boolean forRestore)
@@ -1155,6 +1179,7 @@
         }
     }
 
+    @VisibleForTesting
     void clearNotifications() {
         mEnqueuedNotifications.clear();
         mNotificationList.clear();
@@ -1374,7 +1399,8 @@
                 AppGlobals.getPackageManager(), getContext().getPackageManager(),
                 getLocalService(LightsManager.class),
                 new NotificationListeners(AppGlobals.getPackageManager()),
-                new NotificationAssistants(AppGlobals.getPackageManager()),
+                new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
+                        AppGlobals.getPackageManager()),
                 new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
                 null, snoozeHelper, new NotificationUsageStats(getContext()),
                 new AtomicFile(new File(systemDir, "notification_policy.xml")),
@@ -5553,8 +5579,9 @@
     public class NotificationAssistants extends ManagedServices {
         static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
 
-        public NotificationAssistants(IPackageManager pm) {
-            super(getContext(), mNotificationLock, mUserProfiles, pm);
+        public NotificationAssistants(Context context, Object lock, UserProfiles up,
+                IPackageManager pm) {
+            super(context, lock, up, pm);
         }
 
         @Override
@@ -5659,6 +5686,14 @@
         public boolean isEnabled() {
             return !getServices().isEmpty();
         }
+
+        protected void upgradeXml(final int xmlVersion, final int userId) {
+            if (xmlVersion == 0) {
+                // one time approval of the OOB assistant
+                Slog.d(TAG, "Approving default notification assistant for user " + userId);
+                readDefaultAssistant(userId);
+            }
+        }
     }
 
     public class NotificationListeners extends ManagedServices {
@@ -6072,7 +6107,7 @@
         public static final String USAGE = "help\n"
                 + "allow_listener COMPONENT [user_id]\n"
                 + "disallow_listener COMPONENT [user_id]\n"
-                + "set_assistant COMPONENT\n"
+                + "allow_assistant COMPONENT\n"
                 + "remove_assistant COMPONENT\n"
                 + "allow_dnd PACKAGE\n"
                 + "disallow_dnd PACKAGE";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
new file mode 100644
index 0000000..689c2ce
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 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 static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.UiServiceTestCase;
+import com.android.server.notification.NotificationManagerService.NotificationAssistants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class NotificationAssistantsTest extends UiServiceTestCase {
+
+    @Mock
+    private PackageManager mPm;
+    @Mock
+    private IPackageManager miPm;
+    @Mock
+    private UserManager mUm;
+    @Mock
+    NotificationManagerService mNm;
+
+    NotificationAssistants mAssistants;
+
+    @Mock
+    private ManagedServices.UserProfiles mUserProfiles;
+
+    Object mLock = new Object();
+
+    UserInfo mZero = new UserInfo(0, "zero", 0);
+    UserInfo mTen = new UserInfo(10, "ten", 0);
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        getContext().setMockPackageManager(mPm);
+        getContext().addMockSystemService(Context.USER_SERVICE, mUm);
+        mAssistants = spy(mNm.new NotificationAssistants(getContext(), mLock, mUserProfiles, miPm));
+
+        List<ResolveInfo> approved = new ArrayList<>();
+        ResolveInfo resolve = new ResolveInfo();
+        approved.add(resolve);
+        ServiceInfo info = new ServiceInfo();
+        info.packageName = "a";
+        info.name="a";
+        resolve.serviceInfo = info;
+        when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(approved);
+
+        List<UserInfo> users = new ArrayList<>();
+        users.add(mZero);
+        users.add(mTen);
+        users.add(new UserInfo(11, "11", 0));
+        users.add(new UserInfo(12, "12", 0));
+        for (UserInfo user : users) {
+            when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
+        }
+        when(mUm.getUsers()).thenReturn(users);
+        when(mUm.getUsers(anyBoolean())).thenReturn(users);
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(new int[] {0, 10, 11, 12});
+    }
+
+    @Test
+    public void testXmlUpgrade() throws Exception {
+        String xml = "<enabled_assistants/>";
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        //once per user
+        verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt());
+    }
+
+    @Test
+    public void testXmlUpgradeExistingApprovedComponents() throws Exception {
+        String xml = "<enabled_assistants>"
+                + "<service_listing approved=\"b/b\" user=\"10\" primary=\"true\" />"
+                + "</enabled_assistants>";
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        // once per user
+        verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt());
+        verify(mAssistants, times(1)).addApprovedList(
+                new ComponentName("b", "b").flattenToString(),10, true);
+    }
+
+    @Test
+    public void testXmlUpgradeOnce() throws Exception {
+        String xml = "<enabled_assistants/>";
+
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        XmlSerializer serializer = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mAssistants.writeXml(serializer, true);
+        serializer.endDocument();
+        serializer.flush();
+
+        //once per user
+        verify(mNm, times(mUm.getUsers().size())).readDefaultAssistant(anyInt());
+
+        Mockito.reset(mNm);
+
+        parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        parser.nextTag();
+        mAssistants.readXml(parser);
+
+        //once per user
+        verify(mNm, never()).readDefaultAssistant(anyInt());
+    }
+}