/*
 * Copyright (C) 2017 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 com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
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.Intent;
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.Build;
import android.os.IBinder;
import android.os.IInterface;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Xml;

import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;

import com.google.android.collect.Lists;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
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 ManagedServicesTest extends UiServiceTestCase {

    @Mock
    private IPackageManager mIpm;
    @Mock
    private PackageManager mPm;
    @Mock
    private UserManager mUm;
    @Mock
    private ManagedServices.UserProfiles mUserProfiles;
    Object mLock = new Object();

    UserInfo mZero = new UserInfo(0, "zero", 0);
    UserInfo mTen = new UserInfo(10, "ten", 0);

    private static final String SETTING = "setting";
    private static final String SECONDARY_SETTING = "secondary_setting";

    private ArrayMap<Integer, String> mExpectedPrimaryPackages;
    private ArrayMap<Integer, String> mExpectedPrimaryComponentNames;
    private ArrayMap<Integer, String> mExpectedSecondaryPackages;
    private ArrayMap<Integer, String> mExpectedSecondaryComponentNames;

    // type : user : list of approved
    private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary = new ArrayMap<>();
    private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary = new ArrayMap<>();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        getContext().setMockPackageManager(mPm);
        getContext().addMockSystemService(Context.USER_SERVICE, mUm);

        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(mUserProfiles.getCurrentProfileIds()).thenReturn(new int[] {0, 10, 11, 12});

        mExpectedPrimaryPackages = new ArrayMap<>();
        mExpectedPrimaryPackages.put(0, "this.is.a.package.name:another.package");
        mExpectedPrimaryPackages.put(10, "this.is.another.package");
        mExpectedPrimaryPackages.put(11, "");
        mExpectedPrimaryPackages.put(12, "bananas!");
        mExpectedPrimaryComponentNames = new ArrayMap<>();
        mExpectedPrimaryComponentNames.put(0, "this.is.a.package.name/Ba:another.package/B1");
        mExpectedPrimaryComponentNames.put(10, "this.is.another.package/M1");
        mExpectedPrimaryComponentNames.put(11, "");
        mExpectedPrimaryComponentNames.put(12, "bananas!/Bananas!");
        mExpectedPrimary.put(APPROVAL_BY_PACKAGE, mExpectedPrimaryPackages);
        mExpectedPrimary.put(APPROVAL_BY_COMPONENT, mExpectedPrimaryComponentNames);

        mExpectedSecondaryComponentNames = new ArrayMap<>();
        mExpectedSecondaryComponentNames.put(0, "secondary/component.Name");
        mExpectedSecondaryComponentNames.put(10,
                "this.is.another.package/with.Component:component/2:package/component2");
        mExpectedSecondaryPackages = new ArrayMap<>();
        mExpectedSecondaryPackages.put(0, "secondary");
        mExpectedSecondaryPackages.put(10,
                "this.is.another.package:component:package");
        mExpectedSecondary.put(APPROVAL_BY_PACKAGE, mExpectedSecondaryPackages);
        mExpectedSecondary.put(APPROVAL_BY_COMPONENT, mExpectedSecondaryComponentNames);
    }

    @Test
    public void testBackupAndRestore_migration() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);

            for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
                service.onSettingRestored(
                        service.getConfig().secureSettingName,
                        mExpectedPrimary.get(approvalLevel).get(userId),
                        Build.VERSION_CODES.O, userId);
            }
            verifyExpectedApprovedEntries(service, true);

            for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
                service.onSettingRestored(service.getConfig().secondarySettingName,
                        mExpectedSecondary.get(approvalLevel).get(userId), Build.VERSION_CODES.O,
                        userId);
            }
            verifyExpectedApprovedEntries(service);
        }
    }

    @Test
    public void testBackupAndRestore_migration_preO() throws Exception {
        ArrayMap backupPrimaryPackages = new ArrayMap<>();
        backupPrimaryPackages.put(0, "backup.0:backup:0a");
        backupPrimaryPackages.put(10, "10.backup");
        backupPrimaryPackages.put(11, "eleven");
        backupPrimaryPackages.put(12, "");
        ArrayMap backupPrimaryComponentNames = new ArrayMap<>();
        backupPrimaryComponentNames.put(0, "backup.first/whatever:a/b");
        backupPrimaryComponentNames.put(10, "again/M1");
        backupPrimaryComponentNames.put(11, "orange/youglad:itisnot/banana");
        backupPrimaryComponentNames.put(12, "");
        ArrayMap<Integer, ArrayMap<Integer, String>> backupPrimary = new ArrayMap<>();
        backupPrimary.put(APPROVAL_BY_PACKAGE, backupPrimaryPackages);
        backupPrimary.put(APPROVAL_BY_COMPONENT, backupPrimaryComponentNames);

        ArrayMap backupSecondaryComponentNames = new ArrayMap<>();
        backupSecondaryComponentNames.put(0, "secondary.1/component.Name");
        backupSecondaryComponentNames.put(10,
                "this.is.another.package.backup/with.Component:component.backup/2");
        ArrayMap backupSecondaryPackages = new ArrayMap<>();
        backupSecondaryPackages.put(0, "");
        backupSecondaryPackages.put(10,
                "this.is.another.package.backup:package.backup");
        ArrayMap<Integer, ArrayMap<Integer, String>> backupSecondary = new ArrayMap<>();
        backupSecondary.put(APPROVAL_BY_PACKAGE, backupSecondaryPackages);
        backupSecondary.put(APPROVAL_BY_COMPONENT, backupSecondaryComponentNames);

        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);

            // not an expected flow but a way to get data into the settings
            for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
                service.onSettingRestored(
                        service.getConfig().secureSettingName,
                        mExpectedPrimary.get(approvalLevel).get(userId),
                        Build.VERSION_CODES.O, userId);
            }

            for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
                service.onSettingRestored(service.getConfig().secondarySettingName,
                        mExpectedSecondary.get(approvalLevel).get(userId), Build.VERSION_CODES.O,
                        userId);
            }

            // actual test
            for (int userId : backupPrimary.get(approvalLevel).keySet()) {
                service.onSettingRestored(
                        service.getConfig().secureSettingName,
                        backupPrimary.get(approvalLevel).get(userId),
                        Build.VERSION_CODES.N_MR1, userId);
            }
            verifyExpectedApprovedEntries(service, true);

            for (int userId : backupSecondary.get(approvalLevel).keySet()) {
                service.onSettingRestored(service.getConfig().secondarySettingName,
                        backupSecondary.get(approvalLevel).get(userId),
                        Build.VERSION_CODES.N_MR1, userId);
            }
            verifyExpectedApprovedEntries(service);
            verifyExpectedApprovedEntries(service, backupPrimary.get(approvalLevel));
            verifyExpectedApprovedEntries(service, backupSecondary.get(approvalLevel));
        }
    }

    @Test
    public void testReadXml_migrationFromSettings() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);

            // approved services aren't in xml
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
                    null);
            writeExpectedValuesToSettings(approvalLevel);

            service.migrateToXml();

            verifyExpectedApprovedEntries(service);
        }
    }

    @Test
    public void testReadXml() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);

            loadXml(service);

            verifyExpectedApprovedEntries(service);

            int[] invalidUsers = new int[] {98, 99};
            for (int invalidUser : invalidUsers) {
                assertFalse("service type " + service.mApprovalLevel + ":"
                                + invalidUser + " is allowed for user " + invalidUser,
                        service.isPackageOrComponentAllowed(
                                String.valueOf(invalidUser), invalidUser));
            }
        }
    }

    @Test
    public void testWriteXml_trimsMissingServices() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            loadXml(service);

            // remove missing
            mExpectedPrimaryPackages.put(0, "another.package");
            mExpectedPrimaryPackages.remove(12);
            mExpectedPrimaryComponentNames.put(0, "another.package/B1");
            mExpectedPrimaryComponentNames.remove(12);
            mExpectedSecondaryPackages.put(10, "this.is.another.package:component");
            mExpectedSecondaryComponentNames.put(
                    10, "this.is.another.package/with.Component:component/2");

            for (UserInfo userInfo : mUm.getUsers()) {
                List<String> entriesExpectedToHaveServices = new ArrayList<>();
                if (mExpectedPrimary.get(approvalLevel).containsKey(userInfo.id)) {
                    for (String packageOrComponent :
                            mExpectedPrimary.get(approvalLevel).get(userInfo.id).split(":")) {
                        if (!TextUtils.isEmpty(packageOrComponent)) {
                            entriesExpectedToHaveServices.add(
                                    service.getPackageName(packageOrComponent));
                        }
                    }
                }
                if (mExpectedSecondary.get(approvalLevel).containsKey(userInfo.id)) {
                    for (String packageOrComponent :
                            mExpectedSecondary.get(approvalLevel).get(userInfo.id).split(":")) {
                        if (!TextUtils.isEmpty(packageOrComponent)) {
                            entriesExpectedToHaveServices.add(
                                    service.getPackageName(packageOrComponent));
                        }
                    }
                }
                addExpectedServices(service, entriesExpectedToHaveServices, userInfo.id);
            }

            XmlSerializer serializer = new FastXmlSerializer();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
            serializer.startDocument(null, true);
            service.writeXml(serializer, true);
            serializer.endDocument();
            serializer.flush();

            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(new BufferedInputStream(
                    new ByteArrayInputStream(baos.toByteArray())), null);
            parser.nextTag();
            service.readXml(parser);

            verifyExpectedApprovedEntries(service);
            assertFalse(service.isPackageOrComponentAllowed("this.is.a.package.name", 0));
            assertFalse(service.isPackageOrComponentAllowed("bananas!", 12));
            assertFalse(service.isPackageOrComponentAllowed("package/component2", 10));
        }
    }

    @Test
    public void testWriteXml_writesSetting() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            loadXml(service);

            XmlSerializer serializer = new FastXmlSerializer();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
            serializer.startDocument(null, true);
            service.writeXml(serializer, false);
            serializer.endDocument();
            serializer.flush();

            for (int userId : mUserProfiles.getCurrentProfileIds()) {
                List<String> expected =
                        stringToList(mExpectedPrimary.get(approvalLevel).get(userId));
                List<String> actual = stringToList(Settings.Secure.getStringForUser(
                        getContext().getContentResolver(),
                        service.getConfig().secureSettingName, userId));
                assertContentsInAnyOrder(actual, expected);
            }
        }
    }

    @Test
    public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception {
        // If the primary and secondary lists contain component names, only those components within
        // the package should be matched
        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                mIpm,
                ManagedServices.APPROVAL_BY_COMPONENT);

        List<String> packages = new ArrayList<>();
        packages.add("package");
        packages.add("anotherPackage");
        addExpectedServices(service, packages, 0);

        // only 2 components are approved per package
        mExpectedPrimaryComponentNames.clear();
        mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
        mExpectedSecondaryComponentNames.clear();
        mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2");

        loadXml(service);

        // verify the 2 components per package are enabled (bound)
        verifyExpectedBoundEntries(service, true);
        verifyExpectedBoundEntries(service, false);

        // verify the last component per package is not enabled/we don't try to bind to it
        for (String pkg : packages) {
            ComponentName unapprovedAdditionalComponent =
                    ComponentName.unflattenFromString(pkg + "/C3");
            assertFalse(
                    service.isComponentEnabledForCurrentProfiles(
                            unapprovedAdditionalComponent));
            verify(mIpm, never()).getServiceInfo(
                    eq(unapprovedAdditionalComponent), anyInt(), anyInt());
        }
    }

    @Test
    public void rebindServices_bindsEverythingInAPackage() throws Exception {
        // If the primary and secondary lists contain packages, all components within those packages
        // should be bound
        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
                APPROVAL_BY_PACKAGE);

        List<String> packages = new ArrayList<>();
        packages.add("package");
        packages.add("packagea");
        addExpectedServices(service, packages, 0);

        // 2 approved packages
        mExpectedPrimaryPackages.clear();
        mExpectedPrimaryPackages.put(0, "package");
        mExpectedSecondaryPackages.clear();
        mExpectedSecondaryPackages.put(0, "packagea");

        loadXml(service);

        // verify the 3 components per package are enabled (bound)
        verifyExpectedBoundEntries(service, true);
        verifyExpectedBoundEntries(service, false);
    }

    @Test
    public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            writeExpectedValuesToSettings(approvalLevel);
            service.migrateToXml();

            mExpectedPrimaryPackages.put(0, "another.package");
            mExpectedPrimaryComponentNames.put(0, "another.package/B1");
            service.onPackagesChanged(true, new String[]{"this.is.a.package.name"}, new int[]{103});

            verifyExpectedApprovedEntries(service);
        }
    }

    @Test
    public void testPackageUninstall_componentNoLongerInApprovedList() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            writeExpectedValuesToSettings(approvalLevel);
            service.migrateToXml();

            mExpectedSecondaryComponentNames.put(10, "component/2");
            mExpectedSecondaryPackages.put(10, "component");
            service.onPackagesChanged(true, new String[]{"this.is.another.package"}, new int[]{
                    UserHandle.PER_USER_RANGE + 1});

            verifyExpectedApprovedEntries(service);
        }
    }

    @Test
    public void testSetPackageOrComponentEnabled() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            ArrayMap<Integer, ArrayList<String>> expectedEnabled = new ArrayMap<>();
            expectedEnabled.put(0,
                    Lists.newArrayList(new String[]{"package/Comp", "package/C2", "again/M4"}));
            expectedEnabled.put(10,
                    Lists.newArrayList(new String[]{"user10package/B", "user10/Component",
                            "user10package1/K", "user10.3/Component", "user10package2/L",
                            "user10.4/Component"}));

            for (int userId : expectedEnabled.keySet()) {
                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
                for (int i = 0; i < expectedForUser.size(); i++) {
                    boolean primary = i % 2 == 0;
                    service.setPackageOrComponentEnabled(expectedForUser.get(i), userId, primary,
                            true);
                }
            }

            // verify everything added is approved
            for (int userId : expectedEnabled.keySet()) {
                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
                for (int i = 0; i < expectedForUser.size(); i++) {
                    String verifyValue  = (approvalLevel == APPROVAL_BY_COMPONENT)
                            ? expectedForUser.get(i)
                            : service.getPackageName(expectedForUser.get(i));
                    assertTrue("Not allowed: user: " + userId + " entry: " + verifyValue
                            + " for approval level " + approvalLevel,
                            service.isPackageOrComponentAllowed(verifyValue, userId));
                }
            }

            ArrayMap<Integer, ArrayList<String>> expectedNoAccess = new ArrayMap<>();
            for (int userId : expectedEnabled.keySet()) {
                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
                for (int i = expectedForUser.size() - 1; i >= 0; i--) {
                    ArrayList<String> removed = new ArrayList<>();
                    if (i % 3 == 0) {
                        String revokeAccessFor = expectedForUser.remove(i);
                        removed.add(revokeAccessFor);
                        service.setPackageOrComponentEnabled(
                                revokeAccessFor, userId, i % 2 == 0, false);
                    }
                    expectedNoAccess.put(userId, removed);
                }
            }

            // verify everything still there is approved
            for (int userId : expectedEnabled.keySet()) {
                ArrayList<String> expectedForUser = expectedEnabled.get(userId);
                for (int i = 0; i < expectedForUser.size(); i++) {
                    String verifyValue  = (approvalLevel == APPROVAL_BY_COMPONENT)
                            ? expectedForUser.get(i)
                            : service.getPackageName(expectedForUser.get(i));
                    assertTrue("Not allowed: user: " + userId + " entry: " + verifyValue,
                            service.isPackageOrComponentAllowed(verifyValue, userId));
                }
            }
            // verify everything removed isn't
            for (int userId : expectedNoAccess.keySet()) {
                ArrayList<String> notExpectedForUser = expectedNoAccess.get(userId);
                for (int i = 0; i < notExpectedForUser.size(); i++) {
                    assertFalse(
                            "Is allowed: user: " + userId + " entry: " + notExpectedForUser.get(i),
                            service.isPackageOrComponentAllowed(notExpectedForUser.get(i), userId));
                }
            }
        }
    }

    @Test
    public void testGetAllowedPackages() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            loadXml(service);

            List<String> allowedPackagesForUser0 = new ArrayList<>();
            allowedPackagesForUser0.add("this.is.a.package.name");
            allowedPackagesForUser0.add("another.package");
            allowedPackagesForUser0.add("secondary");

            List<String> actual = service.getAllowedPackages(0);
            assertEquals(3, actual.size());
            for (String pkg : allowedPackagesForUser0) {
                assertTrue(actual.contains(pkg));
            }

            List<String> allowedPackagesForUser10 = new ArrayList<>();
            allowedPackagesForUser10.add("this.is.another.package");
            allowedPackagesForUser10.add("package");
            allowedPackagesForUser10.add("this.is.another.package");
            allowedPackagesForUser10.add("component");

            actual = service.getAllowedPackages(10);
            assertEquals(4, actual.size());
            for (String pkg : allowedPackagesForUser10) {
                assertTrue(actual.contains(pkg));
            }
        }
    }

    @Test
    public void testGetAllowedComponents() throws Exception {
        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
                APPROVAL_BY_COMPONENT);
        loadXml(service);

        List<ComponentName> expected = new ArrayList<>();
        expected.add(ComponentName.unflattenFromString("this.is.another.package/M1"));
        expected.add(ComponentName.unflattenFromString("this.is.another.package/with.Component"));
        expected.add(ComponentName.unflattenFromString("component/2"));
        expected.add(ComponentName.unflattenFromString("package/component2"));

        List<ComponentName> actual = service.getAllowedComponents(10);

        assertContentsInAnyOrder(expected, actual);

        assertEquals(expected.size(), actual.size());

        for (ComponentName cn : expected) {
            assertTrue("Actual missing " + cn, actual.contains(cn));
        }

        for (ComponentName cn : actual) {
            assertTrue("Actual contains extra " + cn, expected.contains(cn));
        }
    }

    @Test
    public void testGetAllowedComponents_approvalByPackage() throws Exception {
        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
                APPROVAL_BY_PACKAGE);
        loadXml(service);

        assertEquals(0, service.getAllowedComponents(10).size());
    }

    @Test
    public void testOnUserRemoved() throws Exception {
        for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
            ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
                    mIpm, approvalLevel);
            loadXml(service);

            ArrayMap<Integer, String> verifyMap = mExpectedPrimary.get(service.mApprovalLevel);
            String user0 = verifyMap.remove(0);
            verifyMap = mExpectedSecondary.get(service.mApprovalLevel);
            user0 = user0 + ":" + verifyMap.remove(0);

            service.onUserRemoved(0);

            for (String verifyValue : user0.split(":")) {
                if (!TextUtils.isEmpty(verifyValue)) {
                    assertFalse("service type " + service.mApprovalLevel + ":" + verifyValue
                            + " is still allowed",
                            service.isPackageOrComponentAllowed(verifyValue, 0));
                }
            }

            verifyExpectedApprovedEntries(service);
        }
    }

    private void loadXml(ManagedServices service) throws Exception {
        final StringBuffer xml = new StringBuffer();
        xml.append("<" + service.getConfig().xmlTag + ">\n");
        for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) {
            xml.append(getXmlEntry(
                    mExpectedPrimary.get(service.mApprovalLevel).get(userId), userId, true));
        }
        for (int userId : mExpectedSecondary.get(service.mApprovalLevel).keySet()) {
            xml.append(getXmlEntry(
                    mExpectedSecondary.get(service.mApprovalLevel).get(userId), userId, false));
        }
        xml.append("<" + ManagedServices.TAG_MANAGED_SERVICES + " "
                        + ManagedServices.ATT_USER_ID + "=\"99\" "
                        + ManagedServices.ATT_IS_PRIMARY + "=\"true\" "
                        + ManagedServices.ATT_APPROVED_LIST + "=\"99\" />\n");
        xml.append("<" + ManagedServices.TAG_MANAGED_SERVICES + " "
                + ManagedServices.ATT_USER_ID + "=\"98\" "
                + ManagedServices.ATT_IS_PRIMARY + "=\"false\" "
                + ManagedServices.ATT_APPROVED_LIST + "=\"98\" />\n");
        xml.append("</" + service.getConfig().xmlTag + ">");

        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new BufferedInputStream(
                new ByteArrayInputStream(xml.toString().getBytes())), null);
        parser.nextTag();
        service.readXml(parser);
    }

    private void addExpectedServices(final ManagedServices service, final List<String> packages,
            int userId) {
        when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))).
                thenAnswer(new Answer<List<ResolveInfo>>() {
                    @Override
                    public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
                            throws Throwable {
                        Object[] args = invocationOnMock.getArguments();
                        Intent invocationIntent = (Intent) args[0];
                        if (invocationIntent != null) {
                            if (invocationIntent.getAction().equals(
                                    service.getConfig().serviceInterface)
                                    && packages.contains(invocationIntent.getPackage())) {
                                List<ResolveInfo> dummyServices = new ArrayList<>();
                                for (int i = 1; i <= 3; i ++) {
                                    ResolveInfo resolveInfo = new ResolveInfo();
                                    ServiceInfo serviceInfo = new ServiceInfo();
                                    serviceInfo.packageName = invocationIntent.getPackage();
                                    serviceInfo.name = "C"+i;
                                    serviceInfo.permission = service.getConfig().bindPermission;
                                    resolveInfo.serviceInfo = serviceInfo;
                                    dummyServices.add(resolveInfo);
                                }
                                return dummyServices;
                            }
                        }
                        return new ArrayList<>();
                    }
                });
    }

    private List<String> stringToList(String list) {
        if (list == null) {
            list = "";
        }
        return new ArrayList<>(Lists.newArrayList(list.split(
                ManagedServices.ENABLED_SERVICES_SEPARATOR)));
    }

    private void assertContentsInAnyOrder(List<?> expected, List<?> actual) {
        assertEquals(expected.size(), actual.size());

        for (Object o : expected) {
            assertTrue("Actual missing " + o, actual.contains(o));
        }

        for (Object o : actual) {
            assertTrue("Actual contains extra " + o, expected.contains(o));
        }
    }

    private void verifyExpectedBoundEntries(ManagedServices service, boolean primary)
            throws Exception {
        ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel)
                : mExpectedSecondary.get(service.mApprovalLevel);
        for (int userId : verifyMap.keySet()) {
            for (String packageOrComponent : verifyMap.get(userId).split(":")) {
                if (!TextUtils.isEmpty(packageOrComponent)) {
                    if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) {
                        assertTrue(packageOrComponent,
                                service.isComponentEnabledForPackage(packageOrComponent));
                        for (int i = 1; i <= 3; i ++) {
                            ComponentName componentName = ComponentName.unflattenFromString(
                                    packageOrComponent +"/C" + i);
                            assertTrue(service.isComponentEnabledForCurrentProfiles(
                                    componentName));
                            verify(mIpm, times(1)).getServiceInfo(
                                    eq(componentName), anyInt(), anyInt());
                        }
                    } else {
                        ComponentName componentName =
                                ComponentName.unflattenFromString(packageOrComponent);
                        assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
                        verify(mIpm, times(1)).getServiceInfo(
                                eq(componentName), anyInt(), anyInt());
                    }
                }
            }
        }
    }

    private void verifyExpectedApprovedEntries(ManagedServices service) {
        verifyExpectedApprovedEntries(service, true);
        verifyExpectedApprovedEntries(service, false);
    }

    private void verifyExpectedApprovedEntries(ManagedServices service, boolean primary) {
        ArrayMap<Integer, String> verifyMap = primary
                ? mExpectedPrimary.get(service.mApprovalLevel)
                : mExpectedSecondary.get(service.mApprovalLevel);
        verifyExpectedApprovedEntries(service, verifyMap);
    }

    private void verifyExpectedApprovedEntries(ManagedServices service,
            ArrayMap<Integer, String> verifyMap) {
        for (int userId : verifyMap.keySet()) {
            for (String verifyValue : verifyMap.get(userId).split(":")) {
                if (!TextUtils.isEmpty(verifyValue)) {
                    assertTrue("service type " + service.mApprovalLevel + ":"
                                    + verifyValue + " is not allowed for user " + userId,
                            service.isPackageOrComponentAllowed(verifyValue, userId));
                }
            }
        }
    }

    private void writeExpectedValuesToSettings(int approvalLevel) {
        for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
            Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING,
                    mExpectedPrimary.get(approvalLevel).get(userId), userId);
        }
        for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
            Settings.Secure.putStringForUser(getContext().getContentResolver(), SECONDARY_SETTING,
                    mExpectedSecondary.get(approvalLevel).get(userId), userId);
        }
    }

    private String getXmlEntry(String approved, int userId, boolean isPrimary) {
        return "<" + ManagedServices.TAG_MANAGED_SERVICES + " "
                + ManagedServices.ATT_USER_ID + "=\"" + userId +"\" "
                + ManagedServices.ATT_IS_PRIMARY + "=\"" + isPrimary +"\" "
                + ManagedServices.ATT_APPROVED_LIST + "=\"" + approved +"\" "
                + "/>\n";
    }

    class TestManagedServices extends ManagedServices {

        public TestManagedServices(Context context, Object mutex, UserProfiles userProfiles,
                IPackageManager pm, int approvedServiceType) {
            super(context, mutex, userProfiles, pm);
            mApprovalLevel = approvedServiceType;
        }

        @Override
        protected Config getConfig() {
            final Config c = new Config();
            c.xmlTag = "test";
            c.secureSettingName = SETTING;
            c.secondarySettingName = SECONDARY_SETTING;
            c.bindPermission = "permission";
            c.serviceInterface = "serviceInterface";
            return c;
        }

        @Override
        protected IInterface asInterface(IBinder binder) {
            return null;
        }

        @Override
        protected boolean checkType(IInterface service) {
            return false;
        }

        @Override
        protected void onServiceAdded(ManagedServiceInfo info) {

        }
    }
}
