/*
 * 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.telecom.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.clearInvocations;
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.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Xml;

import androidx.test.InstrumentationRegistry;

import com.android.internal.telecom.IConnectionService;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.telecom.AppLabelProxy;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneAccountRegistrar.DefaultPhoneAccountHandle;
import com.android.server.telecom.TelecomSystem;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
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.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

@RunWith(JUnit4.class)
public class PhoneAccountRegistrarTest extends TelecomTestCase {

    private static final int MAX_VERSION = Integer.MAX_VALUE;
    private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
    private static final String TEST_LABEL = "right";
    private final String PACKAGE_1 = "PACKAGE_1";
    private final String PACKAGE_2 = "PACKAGE_2";
    private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
    private PhoneAccountRegistrar mRegistrar;
    @Mock private SubscriptionManager mSubscriptionManager;
    @Mock private TelecomManager mTelecomManager;
    @Mock private DefaultDialerCache mDefaultDialerCache;
    @Mock private AppLabelProxy mAppLabelProxy;

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
        MockitoAnnotations.initMocks(this);
        mComponentContextFixture.setTelecomManager(mTelecomManager);
        mComponentContextFixture.setSubscriptionManager(mSubscriptionManager);
        new File(
                mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
                FILE_NAME)
                .delete();
        when(mDefaultDialerCache.getDefaultDialerApplication(anyInt()))
                .thenReturn("com.android.dialer");
        when(mAppLabelProxy.getAppLabel(anyString()))
                .thenReturn(TEST_LABEL);
        mRegistrar = new PhoneAccountRegistrar(
                mComponentContextFixture.getTestDouble().getApplicationContext(),
                mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
    }

    @Override
    @After
    public void tearDown() throws Exception {
        mRegistrar = null;
        new File(
                mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
                FILE_NAME)
                .delete();
        super.tearDown();
    }

    @MediumTest
    @Test
    public void testPhoneAccountHandle() throws Exception {
        PhoneAccountHandle input = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0");
        PhoneAccountHandle result = roundTripXml(this, input,
                PhoneAccountRegistrar.sPhoneAccountHandleXml, mContext);
        assertPhoneAccountHandleEquals(input, result);

        PhoneAccountHandle inputN = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), null);
        PhoneAccountHandle resultN = roundTripXml(this, inputN,
                PhoneAccountRegistrar.sPhoneAccountHandleXml, mContext);
        Log.i(this, "inputN = %s, resultN = %s", inputN, resultN);
        assertPhoneAccountHandleEquals(inputN, resultN);
    }

    @MediumTest
    @Test
    public void testPhoneAccount() throws Exception {
        Bundle testBundle = new Bundle();
        testBundle.putInt("EXTRA_INT_1", 1);
        testBundle.putInt("EXTRA_INT_100", 100);
        testBundle.putBoolean("EXTRA_BOOL_TRUE", true);
        testBundle.putBoolean("EXTRA_BOOL_FALSE", false);
        testBundle.putString("EXTRA_STR1", "Hello");
        testBundle.putString("EXTRA_STR2", "There");

        PhoneAccount input = makeQuickAccountBuilder("id0", 0)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
                .setExtras(testBundle)
                .setIsEnabled(true)
                .build();
        PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
                mContext);

        assertPhoneAccountEquals(input, result);
    }

    @SmallTest
    @Test
    public void testFilterPhoneAccountForTest() throws Exception {
        ComponentName componentA = new ComponentName("a", "a");
        ComponentName componentB1 = new ComponentName("b", "b1");
        ComponentName componentB2 = new ComponentName("b", "b2");
        ComponentName componentC = new ComponentName("c", "c");

        PhoneAccount simAccountA = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentA, "1"), "1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setIsEnabled(true)
                .build();

        List<PhoneAccount> accountAList = new ArrayList<>();
        accountAList.add(simAccountA);

        PhoneAccount simAccountB1 = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentB1, "2"), "2")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setIsEnabled(true)
                .build();

        PhoneAccount simAccountB2 = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentB2, "3"), "3")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setIsEnabled(true)
                .build();

        List<PhoneAccount> accountBList = new ArrayList<>();
        accountBList.add(simAccountB1);
        accountBList.add(simAccountB2);

        PhoneAccount simAccountC = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentC, "4"), "4")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setIsEnabled(true)
                .build();

        List<PhoneAccount> accountCList = new ArrayList<>();
        accountCList.add(simAccountC);

        List<PhoneAccount> allAccounts = new ArrayList<>();
        allAccounts.addAll(accountAList);
        allAccounts.addAll(accountBList);
        allAccounts.addAll(accountCList);

        assertEquals(allAccounts, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));

        mRegistrar.setTestPhoneAccountPackageNameFilter(componentA.getPackageName());
        assertEquals(accountAList, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));

        mRegistrar.setTestPhoneAccountPackageNameFilter(componentB1.getPackageName());
        assertEquals(accountBList, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));

        mRegistrar.setTestPhoneAccountPackageNameFilter(componentC.getPackageName());
        assertEquals(accountCList, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));

        mRegistrar.setTestPhoneAccountPackageNameFilter(null);
        assertEquals(allAccounts, mRegistrar.filterRestrictedPhoneAccounts(allAccounts));
    }

    @MediumTest
    @Test
    public void testDefaultPhoneAccountHandleEmptyGroup() throws Exception {
        DefaultPhoneAccountHandle input = new DefaultPhoneAccountHandle(Process.myUserHandle(),
                makeQuickAccountHandle("i1"), "");
        when(UserManager.get(mContext).getSerialNumberForUser(input.userHandle))
                .thenReturn(0L);
        when(UserManager.get(mContext).getUserForSerialNumber(0L))
                .thenReturn(input.userHandle);
        DefaultPhoneAccountHandle result = roundTripXml(this, input,
                PhoneAccountRegistrar.sDefaultPhoneAcountHandleXml, mContext);

        assertDefaultPhoneAccountHandleEquals(input, result);
    }

    /**
     * Test to ensure non-supported values
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testPhoneAccountExtrasEdge() throws Exception {
        Bundle testBundle = new Bundle();
        // Ensure null values for string are not persisted.
        testBundle.putString("EXTRA_STR2", null);
        //

        // Ensure unsupported data types are not persisted.
        testBundle.putShort("EXTRA_SHORT", (short) 2);
        testBundle.putByte("EXTRA_BYTE", (byte) 1);
        testBundle.putParcelable("EXTRA_PARC", new Rect(1, 1, 1, 1));
        // Put in something valid so the bundle exists.
        testBundle.putString("EXTRA_OK", "OK");

        PhoneAccount input = makeQuickAccountBuilder("id0", 0)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
                .setExtras(testBundle)
                .build();
        PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
                mContext);

        Bundle extras = result.getExtras();
        assertFalse(extras.keySet().contains("EXTRA_STR2"));
        assertFalse(extras.keySet().contains("EXTRA_SHORT"));
        assertFalse(extras.keySet().contains("EXTRA_BYTE"));
        assertFalse(extras.keySet().contains("EXTRA_PARC"));
    }

    @MediumTest
    @Test
    public void testState() throws Exception {
        PhoneAccountRegistrar.State input = makeQuickState();
        PhoneAccountRegistrar.State result = roundTripXml(this, input,
                PhoneAccountRegistrar.sStateXml,
                mContext);
        assertStateEquals(input, result);
    }

    private void registerAndEnableAccount(PhoneAccount account) {
        mRegistrar.registerPhoneAccount(account);
        mRegistrar.enablePhoneAccount(account.getAccountHandle(), true);
    }

    @MediumTest
    @Test
    public void testAccounts() throws Exception {
        int i = 0;

        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                        | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build());
        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                        | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build());
        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                        | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build());
        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
                .build());

        assertEquals(4, mRegistrar.getAllPhoneAccountsOfCurrentUser().size());
        assertEquals(3, mRegistrar.getCallCapablePhoneAccountsOfCurrentUser(null, false).size());
        assertEquals(null, mRegistrar.getSimCallManagerOfCurrentUser());
        assertEquals(null, mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL));
    }

    @MediumTest
    @Test
    public void testSimCallManager() throws Exception {
        // TODO
    }

    @MediumTest
    @Test
    public void testDefaultOutgoing() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        // By default, there is no default outgoing account (nothing has been registered)
        assertNull(
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));

        // Register one tel: account
        PhoneAccountHandle telAccount = makeQuickAccountHandle("tel_acct");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount, "tel_acct")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .build());
        PhoneAccountHandle defaultAccount =
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount, defaultAccount);

        // Add a SIP account, make sure tel: doesn't change
        PhoneAccountHandle sipAccount = makeQuickAccountHandle("sip_acct");
        registerAndEnableAccount(new PhoneAccount.Builder(sipAccount, "sip_acct")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
                .build());
        defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_SIP);
        assertEquals(sipAccount, defaultAccount);
        defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount, defaultAccount);

        // Add a connection manager, make sure tel: doesn't change
        PhoneAccountHandle connectionManager = makeQuickAccountHandle("mgr_acct");
        registerAndEnableAccount(new PhoneAccount.Builder(connectionManager, "mgr_acct")
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .build());
        defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount, defaultAccount);

        // Unregister the tel: account, make sure there is no tel: default now.
        mRegistrar.unregisterPhoneAccount(telAccount);
        assertNull(
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));
    }

    @MediumTest
    @Test
    public void testReplacePhoneAccountByGroup() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        // By default, there is no default outgoing account (nothing has been registered)
        assertNull(
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));

        // Register one tel: account
        PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
        PhoneAccountHandle defaultAccount =
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount1, defaultAccount);

        // Add call capable SIP account, make sure tel: doesn't change
        PhoneAccountHandle sipAccount = makeQuickAccountHandle("sip_acct");
        registerAndEnableAccount(new PhoneAccount.Builder(sipAccount, "sip_acct")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .build());
        defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount1, defaultAccount);

        // Replace tel: account with another in the same Group
        PhoneAccountHandle telAccount2 = makeQuickAccountHandle("tel_acct2");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount2, defaultAccount);
        assertNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
    }

    @MediumTest
    @Test
    public void testAddSameDefault() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        // By default, there is no default outgoing account (nothing has been registered)
        assertNull(
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));

        // Register one tel: account
        PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
        PhoneAccountHandle defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertEquals(telAccount1, defaultAccount);
        mRegistrar.unregisterPhoneAccount(telAccount1);

        // Register Emergency Account and unregister
        PhoneAccountHandle emerAccount = makeQuickAccountHandle("emer_acct");
        registerAndEnableAccount(new PhoneAccount.Builder(emerAccount, "emer_acct")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .build());
        defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertNull(defaultAccount);
        mRegistrar.unregisterPhoneAccount(emerAccount);

        // Re-register the same account and make sure the default is in place
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertEquals(telAccount1, defaultAccount);
    }

    @MediumTest
    @Test
    public void testAddSameGroup() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        // By default, there is no default outgoing account (nothing has been registered)
        assertNull(
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));

        // Register one tel: account
        PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
        PhoneAccountHandle defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertEquals(telAccount1, defaultAccount);
        mRegistrar.unregisterPhoneAccount(telAccount1);

        // Register Emergency Account and unregister
        PhoneAccountHandle emerAccount = makeQuickAccountHandle("emer_acct");
        registerAndEnableAccount(new PhoneAccount.Builder(emerAccount, "emer_acct")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .build());
        defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertNull(defaultAccount);
        mRegistrar.unregisterPhoneAccount(emerAccount);

        // Re-register a new account with the same group
        PhoneAccountHandle telAccount2 = makeQuickAccountHandle("tel_acct2");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertEquals(telAccount2, defaultAccount);
    }

    @MediumTest
    @Test
    public void testAddSameGroupButDifferentComponent() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        // By default, there is no default outgoing account (nothing has been registered)
        assertNull(mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL));

        // Register one tel: account
        PhoneAccountHandle telAccount1 = makeQuickAccountHandle("tel_acct1");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());
        PhoneAccountHandle defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertEquals(telAccount1, defaultAccount);
        assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));

        // Register a new account with the same group, but different Component, so don't replace
        // Default
        PhoneAccountHandle telAccount2 =  makeQuickAccountHandle(
                new ComponentName("other1", "other2"), "tel_acct2");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount2));

        defaultAccount =
                mRegistrar.getUserSelectedOutgoingPhoneAccount(Process.myUserHandle());
        assertEquals(telAccount1, defaultAccount);
    }

    @MediumTest
    @Test
    public void testAddSameGroupButDifferentComponent2() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        // By default, there is no default outgoing account (nothing has been registered)
        assertNull(mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                PhoneAccount.SCHEME_TEL));

        // Register first tel: account
        PhoneAccountHandle telAccount1 =  makeQuickAccountHandle(
                new ComponentName("other1", "other2"), "tel_acct1");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount1, "tel_acct1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());
        assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
        mRegistrar.setUserSelectedOutgoingPhoneAccount(telAccount1, Process.myUserHandle());

        // Register second account with the same group, but a second Component, so don't replace
        // Default
        PhoneAccountHandle telAccount2 = makeQuickAccountHandle("tel_acct2");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount2, "tel_acct2")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());

        PhoneAccountHandle defaultAccount =
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount1, defaultAccount);

        // Register third account with the second component name, but same group id
        PhoneAccountHandle telAccount3 = makeQuickAccountHandle("tel_acct3");
        registerAndEnableAccount(new PhoneAccount.Builder(telAccount3, "tel_acct3")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                .setGroupId("testGroup")
                .build());

        // Make sure that the default account is still the original PhoneAccount and that the
        // second PhoneAccount with the second ComponentName was replaced by the third PhoneAccount
        defaultAccount =
                mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
        assertEquals(telAccount1, defaultAccount);

        assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount1));
        assertNull(mRegistrar.getPhoneAccountUnchecked(telAccount2));
        assertNotNull(mRegistrar.getPhoneAccountUnchecked(telAccount3));
    }

    @MediumTest
    @Test
    public void testPhoneAccountParceling() throws Exception {
        PhoneAccountHandle handle = makeQuickAccountHandle("foo");
        roundTripPhoneAccount(new PhoneAccount.Builder(handle, null).build());
        roundTripPhoneAccount(new PhoneAccount.Builder(handle, "foo").build());
        roundTripPhoneAccount(
                new PhoneAccount.Builder(handle, "foo")
                        .setAddress(Uri.parse("tel:123456"))
                        .setCapabilities(23)
                        .setHighlightColor(0xf0f0f0)
                        .setIcon(Icon.createWithResource(
                                "com.android.server.telecom.tests", R.drawable.stat_sys_phone_call))
                        // TODO: set icon tint (0xfefefe)
                        .setShortDescription("short description")
                        .setSubscriptionAddress(Uri.parse("tel:2345678"))
                        .setSupportedUriSchemes(Arrays.asList("tel", "sip"))
                        .setGroupId("testGroup")
                        .build());
        roundTripPhoneAccount(
                new PhoneAccount.Builder(handle, "foo")
                        .setAddress(Uri.parse("tel:123456"))
                        .setCapabilities(23)
                        .setHighlightColor(0xf0f0f0)
                        .setIcon(Icon.createWithBitmap(
                                BitmapFactory.decodeResource(
                                        InstrumentationRegistry.getContext().getResources(),
                                        R.drawable.stat_sys_phone_call)))
                        .setShortDescription("short description")
                        .setSubscriptionAddress(Uri.parse("tel:2345678"))
                        .setSupportedUriSchemes(Arrays.asList("tel", "sip"))
                        .setGroupId("testGroup")
                        .build());
    }

    /**
     * Tests ability to register a self-managed PhoneAccount; verifies that the user defined label
     * is overridden.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testSelfManagedPhoneAccount() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        PhoneAccountHandle selfManagedHandle =  makeQuickAccountHandle(
                new ComponentName("self", "managed"), "selfie1");

        PhoneAccount selfManagedAccount = new PhoneAccount.Builder(selfManagedHandle, "Wrong")
                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
                .build();

        mRegistrar.registerPhoneAccount(selfManagedAccount);

        PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(selfManagedHandle);
        assertEquals(TEST_LABEL, registeredAccount.getLabel());
    }

    /**
     * Tests to ensure that when registering a self-managed PhoneAccount, it cannot also be defined
     * as a call provider, connection manager, or sim subscription.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testSelfManagedCapabilityOverride() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        PhoneAccountHandle selfManagedHandle =  makeQuickAccountHandle(
                new ComponentName("self", "managed"), "selfie1");

        PhoneAccount selfManagedAccount = new PhoneAccount.Builder(selfManagedHandle, TEST_LABEL)
                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
                        PhoneAccount.CAPABILITY_CALL_PROVIDER |
                        PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                .build();

        mRegistrar.registerPhoneAccount(selfManagedAccount);

        PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(selfManagedHandle);
        assertEquals(PhoneAccount.CAPABILITY_SELF_MANAGED, registeredAccount.getCapabilities());
    }

    @MediumTest
    @Test
    public void testSortSimFirst() throws Exception {
        ComponentName componentA = new ComponentName("a", "a");
        ComponentName componentB = new ComponentName("b", "b");
        mComponentContextFixture.addConnectionService(componentA,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentB,
                Mockito.mock(IConnectionService.class));

        PhoneAccount simAccount = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentB, "2"), "2")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                .setIsEnabled(true)
                .build();

        PhoneAccount nonSimAccount = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentA, "1"), "1")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setIsEnabled(true)
                .build();

        registerAndEnableAccount(nonSimAccount);
        registerAndEnableAccount(simAccount);

        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
        assertTrue(accounts.get(0).getLabel().toString().equals("2"));
        assertTrue(accounts.get(1).getLabel().toString().equals("1"));
    }

    @MediumTest
    @Test
    public void testSortBySortOrder() throws Exception {
        ComponentName componentA = new ComponentName("a", "a");
        ComponentName componentB = new ComponentName("b", "b");
        ComponentName componentC = new ComponentName("c", "c");
        mComponentContextFixture.addConnectionService(componentA,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentB,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentC,
                Mockito.mock(IConnectionService.class));

        Bundle account1Extras = new Bundle();
        account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
        PhoneAccount account1 = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentA, "c"), "c")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setExtras(account1Extras)
                .build();

        Bundle account2Extras = new Bundle();
        account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
        PhoneAccount account2 = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentB, "b"), "b")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setExtras(account2Extras)
                .build();

        PhoneAccount account3 = new PhoneAccount.Builder(
                makeQuickAccountHandle(componentC, "c"), "a")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build();

        registerAndEnableAccount(account3);
        registerAndEnableAccount(account2);
        registerAndEnableAccount(account1);

        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
        assertTrue(accounts.get(0).getLabel().toString().equals("c"));
        assertTrue(accounts.get(1).getLabel().toString().equals("b"));
        assertTrue(accounts.get(2).getLabel().toString().equals("a"));
    }

    @MediumTest
    @Test
    public void testSortByLabel() throws Exception {
        ComponentName componentA = new ComponentName("a", "a");
        ComponentName componentB = new ComponentName("b", "b");
        ComponentName componentC = new ComponentName("c", "c");
        mComponentContextFixture.addConnectionService(componentA,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentB,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentC,
                Mockito.mock(IConnectionService.class));

        PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(componentA, "c"),
                "c")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build();

        PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(componentB, "b"),
                "b")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build();

        PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(componentC, "a"),
                "a")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build();

        registerAndEnableAccount(account1);
        registerAndEnableAccount(account2);
        registerAndEnableAccount(account3);

        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
        assertTrue(accounts.get(0).getLabel().toString().equals("a"));
        assertTrue(accounts.get(1).getLabel().toString().equals("b"));
        assertTrue(accounts.get(2).getLabel().toString().equals("c"));
    }

    @MediumTest
    @Test
    public void testSortAll() throws Exception {
        ComponentName componentA = new ComponentName("a", "a");
        ComponentName componentB = new ComponentName("b", "b");
        ComponentName componentC = new ComponentName("c", "c");
        ComponentName componentW = new ComponentName("w", "w");
        ComponentName componentX = new ComponentName("x", "x");
        ComponentName componentY = new ComponentName("y", "y");
        ComponentName componentZ = new ComponentName("z", "z");
        mComponentContextFixture.addConnectionService(componentA,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentB,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentC,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentW,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentX,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentY,
                Mockito.mock(IConnectionService.class));
        mComponentContextFixture.addConnectionService(componentZ,
                Mockito.mock(IConnectionService.class));

        Bundle account1Extras = new Bundle();
        account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
        PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(
                makeQuickConnectionServiceComponentName(), "y"), "y")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                .setExtras(account1Extras)
                .build();

        Bundle account2Extras = new Bundle();
        account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
        PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(
                makeQuickConnectionServiceComponentName(), "z"), "z")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                .setExtras(account2Extras)
                .build();

        PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(
                makeQuickConnectionServiceComponentName(), "x"), "x")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                .build();

        PhoneAccount account4 = new PhoneAccount.Builder(makeQuickAccountHandle(
                makeQuickConnectionServiceComponentName(), "w"), "w")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                .build();

        PhoneAccount account5 = new PhoneAccount.Builder(makeQuickAccountHandle(
                makeQuickConnectionServiceComponentName(), "b"), "b")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build();

        PhoneAccount account6 = new PhoneAccount.Builder(makeQuickAccountHandle(
                makeQuickConnectionServiceComponentName(), "c"), "a")
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build();

        registerAndEnableAccount(account1);
        registerAndEnableAccount(account2);
        registerAndEnableAccount(account3);
        registerAndEnableAccount(account4);
        registerAndEnableAccount(account5);
        registerAndEnableAccount(account6);

        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
        // Sim accts ordered by sort order first
        assertTrue(accounts.get(0).getLabel().toString().equals("z"));
        assertTrue(accounts.get(1).getLabel().toString().equals("y"));

        // Sim accts with no sort order next
        assertTrue(accounts.get(2).getLabel().toString().equals("w"));
        assertTrue(accounts.get(3).getLabel().toString().equals("x"));

        // Other accts sorted by label next
        assertTrue(accounts.get(4).getLabel().toString().equals("a"));
        assertTrue(accounts.get(5).getLabel().toString().equals("b"));
    }

    /**
     * Tests {@link PhoneAccountRegistrar#getCallCapablePhoneAccounts(String, boolean, UserHandle)}
     * to ensure disabled accounts are filtered out of results when requested.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testGetByEnabledState() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));
        mRegistrar.registerPhoneAccount(makeQuickAccountBuilder("id1", 1)
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .build());

        assertEquals(0, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
                false /* includeDisabled */, Process.myUserHandle()).size());
        assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
                true /* includeDisabled */, Process.myUserHandle()).size());
    }

    /**
     * Tests {@link PhoneAccountRegistrar#getCallCapablePhoneAccounts(String, boolean, UserHandle)}
     * to ensure scheme filtering operates.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testGetByScheme() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));
        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1)
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                .build());
        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2)
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
                .build());

        assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
                false /* includeDisabled */, Process.myUserHandle()).size());
        assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
                false /* includeDisabled */, Process.myUserHandle()).size());
        assertEquals(2, mRegistrar.getCallCapablePhoneAccounts(null, false /* includeDisabled */,
                Process.myUserHandle()).size());
    }

    /**
     * Tests {@link PhoneAccountRegistrar#getCallCapablePhoneAccounts(String, boolean, UserHandle,
     * int)} to ensure capability filtering operates.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testGetByCapability() throws Exception {
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));
        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1)
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
                        | PhoneAccount.CAPABILITY_VIDEO_CALLING)
                .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                .build());
        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2)
                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                .build());

        assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
                false /* includeDisabled */, Process.myUserHandle()).size(),
                PhoneAccount.CAPABILITY_VIDEO_CALLING);
        assertEquals(2, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
                false /* includeDisabled */, Process.myUserHandle()).size(), 0 /* none extra */);
        assertEquals(0, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
                false /* includeDisabled */, Process.myUserHandle()).size(),
                PhoneAccount.CAPABILITY_RTT);
    }

    /**
     * Tests {@link PhoneAccount#equals(Object)} operator.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testPhoneAccountEquality() throws Exception {
        PhoneAccountHandle handle = new PhoneAccountHandle(new ComponentName("foo", "bar"), "id");
        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, "label");
        builder.addSupportedUriScheme("tel");
        builder.setAddress(Uri.fromParts("tel", "6505551212", null));
        builder.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER);
        Bundle extras = new Bundle();
        extras.putInt("INT", 1);
        extras.putString("STR", "str");
        builder.setExtras(extras);
        builder.setGroupId("group");
        builder.setHighlightColor(1);
        builder.setShortDescription("short");
        builder.setSubscriptionAddress(Uri.fromParts("tel", "6505551213", null));
        builder.setSupportedAudioRoutes(2);

        PhoneAccount account1 = builder.build();
        PhoneAccount account2 = builder.build();
        assertEquals(account1, account2);
    }

    /**
     * Tests {@link PhoneAccountHandle#areFromSamePackage(PhoneAccountHandle,
     * PhoneAccountHandle)} comparison.
     */
    @SmallTest
    @Test
    public void testSamePhoneAccountHandlePackage() {
        PhoneAccountHandle a = new PhoneAccountHandle(new ComponentName("packageA", "class1"),
                "id1");
        PhoneAccountHandle b = new PhoneAccountHandle(new ComponentName("packageA", "class2"),
                "id2");
        PhoneAccountHandle c = new PhoneAccountHandle(new ComponentName("packageA", "class1"),
                "id3");
        PhoneAccountHandle d = new PhoneAccountHandle(new ComponentName("packageB", "class1"),
                "id1");

        assertTrue(PhoneAccountHandle.areFromSamePackage(null, null));
        assertTrue(PhoneAccountHandle.areFromSamePackage(a, b));
        assertTrue(PhoneAccountHandle.areFromSamePackage(a, c));
        assertTrue(PhoneAccountHandle.areFromSamePackage(b, c));
        assertFalse(PhoneAccountHandle.areFromSamePackage(a, d));
        assertFalse(PhoneAccountHandle.areFromSamePackage(b, d));
        assertFalse(PhoneAccountHandle.areFromSamePackage(c, d));
        assertFalse(PhoneAccountHandle.areFromSamePackage(a, null));
        assertFalse(PhoneAccountHandle.areFromSamePackage(b, null));
        assertFalse(PhoneAccountHandle.areFromSamePackage(c, null));
        assertFalse(PhoneAccountHandle.areFromSamePackage(null, d));
        assertFalse(PhoneAccountHandle.areFromSamePackage(null, d));
        assertFalse(PhoneAccountHandle.areFromSamePackage(null, d));
    }

    /**
     * Tests {@link PhoneAccountRegistrar#cleanupOrphanedPhoneAccounts } cleans up / deletes an
     * orphan account.
     */
    @Test
    public void testCleanUpOrphanAccounts() throws Exception {
        // GIVEN
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        List<UserHandle> users = Arrays.asList(new UserHandle(0),
                new UserHandle(1000));

        PhoneAccount pa1 = new PhoneAccount.Builder(
                new PhoneAccountHandle(new ComponentName(PACKAGE_1, COMPONENT_NAME), "1234",
                        users.get(0)), "l1").build();
        PhoneAccount pa2 = new PhoneAccount.Builder(
                new PhoneAccountHandle(new ComponentName(PACKAGE_2, COMPONENT_NAME), "5678",
                        users.get(1)), "l2").build();


        registerAndEnableAccount(pa1);
        registerAndEnableAccount(pa2);

        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(0)).size());
        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(1)).size());


        // WHEN
        when(mContext.getPackageManager().getPackageInfo(PACKAGE_1, 0))
                .thenReturn(new PackageInfo());

        when(mContext.getPackageManager().getPackageInfo(PACKAGE_2, 0))
                .thenThrow(new PackageManager.NameNotFoundException());

        when(UserManager.get(mContext).getSerialNumberForUser(users.get(0)))
                .thenReturn(0L);

        when(UserManager.get(mContext).getSerialNumberForUser(users.get(1)))
                .thenReturn(-1L);

        // THEN
        int deletedAccounts = mRegistrar.cleanupOrphanedPhoneAccounts();
        assertEquals(1, deletedAccounts);
    }

    @Test
    public void testGetSimPhoneAccountsFromSimCallManager() throws Exception {
        // Register the SIM PhoneAccounts
        mComponentContextFixture.addConnectionService(
                makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class));
        PhoneAccount sim1Account = makeQuickSimAccount(1);
        PhoneAccountHandle sim1Handle = sim1Account.getAccountHandle();
        registerAndEnableAccount(sim1Account);
        PhoneAccount sim2Account = makeQuickSimAccount(2);
        PhoneAccountHandle sim2Handle = sim2Account.getAccountHandle();
        registerAndEnableAccount(sim2Account);

        assertEquals(
            List.of(sim1Handle, sim2Handle), mRegistrar.getSimPhoneAccountsOfCurrentUser());

        // Set up the SIM call manager app + carrier configs
        ComponentName simCallManagerComponent =
                new ComponentName("com.carrier.app", "CarrierConnectionService");
        PhoneAccountHandle simCallManagerHandle =
                makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager");
        setSimCallManagerCarrierConfig(
                1, new ComponentName("com.other.carrier", "OtherConnectionService"));
        setSimCallManagerCarrierConfig(2, simCallManagerComponent);

        // Since SIM 1 names another app, so we only get the handle for SIM 2
        assertEquals(
                List.of(sim2Handle),
                mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle));
        // We do exact component matching, not just package name matching
        assertEquals(
                List.of(),
                mRegistrar.getSimPhoneAccountsFromSimCallManager(
                        makeQuickAccountHandle(
                                new ComponentName("com.carrier.app", "SomeOtherUnrelatedService"),
                                "same-pkg-but-diff-svc")));

        // Results are identical after we register the PhoneAccount
        mComponentContextFixture.addConnectionService(
                simCallManagerComponent, Mockito.mock(IConnectionService.class));
        PhoneAccount simCallManagerAccount =
                new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager")
                        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
                        .build();
        mRegistrar.registerPhoneAccount(simCallManagerAccount);
        assertEquals(
                List.of(sim2Handle),
                mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle));
    }

    @Test
    public void testMaybeNotifyTelephonyForVoiceServiceState() throws Exception {
        // Register the SIM PhoneAccounts
        mComponentContextFixture.addConnectionService(
                makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class));
        PhoneAccount sim1Account = makeQuickSimAccount(1);
        registerAndEnableAccount(sim1Account);
        PhoneAccount sim2Account = makeQuickSimAccount(2);
        registerAndEnableAccount(sim2Account);
        // Telephony is notified by default when new SIM accounts are registered
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(false);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Set up the SIM call manager app + carrier configs
        ComponentName simCallManagerComponent =
                new ComponentName("com.carrier.app", "CarrierConnectionService");
        PhoneAccountHandle simCallManagerHandle =
                makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager");
        mComponentContextFixture.addConnectionService(
                simCallManagerComponent, Mockito.mock(IConnectionService.class));
        setSimCallManagerCarrierConfig(1, simCallManagerComponent);
        setSimCallManagerCarrierConfig(2, simCallManagerComponent);

        // When the SIM call manager is registered without the SUPPORTS capability, telephony is
        // still notified for consistency (e.g. runtime capability removal + re-registration).
        PhoneAccount simCallManagerAccount =
                new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager")
                        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
                        .build();
        mRegistrar.registerPhoneAccount(simCallManagerAccount);
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(false);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Adding the SUPPORTS capability causes the SIMs to get notified with false again for
        // consistency purposes
        simCallManagerAccount =
                copyPhoneAccountAndAddCapabilities(
                        simCallManagerAccount,
                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS);
        mRegistrar.registerPhoneAccount(simCallManagerAccount);
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(false);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Adding the AVAILABLE capability updates the SIMs again, this time with hasService = true
        simCallManagerAccount =
                copyPhoneAccountAndAddCapabilities(
                        simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE);
        mRegistrar.registerPhoneAccount(simCallManagerAccount);
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(true);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Removing a SIM account does nothing, regardless of SIM call manager capabilities
        mRegistrar.unregisterPhoneAccount(sim1Account.getAccountHandle());
        verify(mComponentContextFixture.getTelephonyManager(), never())
                .setVoiceServiceStateOverride(anyBoolean());
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Adding a SIM account while a SIM call manager with both capabilities is registered causes
        // a call to telephony with hasService = true
        mRegistrar.registerPhoneAccount(sim1Account);
        verify(mComponentContextFixture.getTelephonyManager(), times(1))
                .setVoiceServiceStateOverride(true);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Removing the SIM call manager while it has both capabilities causes a call to telephony
        // with hasService = false
        mRegistrar.unregisterPhoneAccount(simCallManagerHandle);
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(false);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Removing the SIM call manager while it has the SUPPORTS capability but not AVAILABLE
        // still causes a call to telephony with hasService = false for consistency
        simCallManagerAccount =
                copyPhoneAccountAndRemoveCapabilities(
                        simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE);
        mRegistrar.registerPhoneAccount(simCallManagerAccount);
        clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration
        mRegistrar.unregisterPhoneAccount(simCallManagerHandle);
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(false);
        clearInvocations(mComponentContextFixture.getTelephonyManager());

        // Finally, removing the SIM call manager while it has neither capability still causes a
        // call to telephony with hasService = false for consistency
        simCallManagerAccount =
                copyPhoneAccountAndRemoveCapabilities(
                        simCallManagerAccount,
                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS);
        mRegistrar.registerPhoneAccount(simCallManagerAccount);
        clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration
        mRegistrar.unregisterPhoneAccount(simCallManagerHandle);
        verify(mComponentContextFixture.getTelephonyManager(), times(2))
                .setVoiceServiceStateOverride(false);
        clearInvocations(mComponentContextFixture.getTelephonyManager());
    }

    /**
     * Test PhoneAccountHandle Migration Logic.
     */
    @Test
    public void testPhoneAccountMigration() throws Exception {
        PhoneAccountRegistrar.State testState = makeQuickStateWithTelephonyPhoneAccountHandle();
        final int mTestPhoneAccountHandleSubIdInt = 123;
        // Mock SubscriptionManager
        SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
                mTestPhoneAccountHandleSubIdInt, "id0", 1, "a", "b", 1, 1, "test",
                        1, null, null, null, null, false, null, null);
        List<SubscriptionInfo> subscriptionInfoList = new ArrayList<>();
        subscriptionInfoList.add(subscriptionInfo);
        when(mSubscriptionManager.getAllSubscriptionInfoList()).thenReturn(subscriptionInfoList);
        mRegistrar.migratePhoneAccountHandle(testState);
        Collection<DefaultPhoneAccountHandle> defaultPhoneAccountHandles
                = testState.defaultOutgoingAccountHandles.values();
        DefaultPhoneAccountHandle defaultPhoneAccountHandle
                = defaultPhoneAccountHandles.iterator().next();
        assertEquals(Integer.toString(mTestPhoneAccountHandleSubIdInt),
                defaultPhoneAccountHandle.phoneAccountHandle.getId());
    }

    private static ComponentName makeQuickConnectionServiceComponentName() {
        return new ComponentName(
                "com.android.server.telecom.tests",
                "com.android.server.telecom.tests.MockConnectionService");
    }

    private static PhoneAccountHandle makeQuickAccountHandle(String id) {
        return makeQuickAccountHandle(makeQuickConnectionServiceComponentName(), id);
    }

    private static PhoneAccountHandle makeQuickAccountHandle(ComponentName name, String id) {
        return new PhoneAccountHandle(name, id, Process.myUserHandle());
    }

    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
        return new PhoneAccount.Builder(
                makeQuickAccountHandle(id),
                "label" + idx);
    }

    private static PhoneAccount copyPhoneAccountAndOverrideCapabilities(
            PhoneAccount base, int newCapabilities) {
        return base.toBuilder().setCapabilities(newCapabilities).build();
    }

    private static PhoneAccount copyPhoneAccountAndAddCapabilities(
            PhoneAccount base, int capabilitiesToAdd) {
        return copyPhoneAccountAndOverrideCapabilities(
                base, base.getCapabilities() | capabilitiesToAdd);
    }

    private static PhoneAccount copyPhoneAccountAndRemoveCapabilities(
            PhoneAccount base, int capabilitiesToRemove) {
        return copyPhoneAccountAndOverrideCapabilities(
                base, base.getCapabilities() & ~capabilitiesToRemove);
    }

    private PhoneAccount makeQuickAccount(String id, int idx) {
        return makeQuickAccountBuilder(id, idx)
                .setAddress(Uri.parse("http://foo.com/" + idx))
                .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
                .setCapabilities(idx)
                .setIcon(Icon.createWithResource(
                            "com.android.server.telecom.tests", R.drawable.stat_sys_phone_call))
                .setShortDescription("desc" + idx)
                .setIsEnabled(true)
                .build();
    }

    /**
     * Similar to {@link #makeQuickAccount}, but also hooks up {@code TelephonyManager} so that it
     * returns {@code simId} as the account's subscriptionId.
     */
    private PhoneAccount makeQuickSimAccount(int simId) {
        PhoneAccount simAccount =
                makeQuickAccountBuilder("sim" + simId, simId)
                        .setCapabilities(
                                PhoneAccount.CAPABILITY_CALL_PROVIDER
                                        | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                        .setIsEnabled(true)
                        .build();
        when(mComponentContextFixture
                        .getTelephonyManager()
                        .getSubscriptionId(simAccount.getAccountHandle()))
                .thenReturn(simId);
        // mComponentContextFixture already sets up the createForSubscriptionId self-reference
        when(mComponentContextFixture
                        .getTelephonyManager()
                        .createForPhoneAccountHandle(simAccount.getAccountHandle()))
                .thenReturn(mComponentContextFixture.getTelephonyManager());
        return simAccount;
    }

    /**
     * Hooks up carrier config to point to {@code simCallManagerComponent} for the given {@code
     * subscriptionId}.
     */
    private void setSimCallManagerCarrierConfig(
            int subscriptionId, @Nullable ComponentName simCallManagerComponent) {
        PersistableBundle config = new PersistableBundle();
        config.putString(
                CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING,
                simCallManagerComponent != null ? simCallManagerComponent.flattenToString() : null);
        when(mComponentContextFixture.getCarrierConfigManager().getConfigForSubId(subscriptionId))
                .thenReturn(config);
    }

    private static void roundTripPhoneAccount(PhoneAccount original) throws Exception {
        PhoneAccount copy = null;

        {
            Parcel parcel = Parcel.obtain();
            parcel.writeParcelable(original, 0);
            parcel.setDataPosition(0);
            copy = parcel.readParcelable(PhoneAccountRegistrarTest.class.getClassLoader());
            parcel.recycle();
        }

        assertPhoneAccountEquals(original, copy);
    }

    private static <T> T roundTripXml(
            Object self,
            T input,
            PhoneAccountRegistrar.XmlSerialization<T> xml,
            Context context)
            throws Exception {
        Log.d(self, "Input = %s", input);

        byte[] data;
        {
            XmlSerializer serializer = new FastXmlSerializer();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
            xml.writeToXml(input, serializer, context);
            serializer.flush();
            data = baos.toByteArray();
        }

        Log.i(self, "====== XML data ======\n%s", new String(data));

        T result = null;
        {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(new BufferedInputStream(new ByteArrayInputStream(data)), null);
            parser.nextTag();
            result = xml.readFromXml(parser, MAX_VERSION, context);
        }

        Log.i(self, "result = " + result);

        return result;
    }

    private static void assertPhoneAccountHandleEquals(PhoneAccountHandle a, PhoneAccountHandle b) {
        if (a != b) {
            assertEquals(
                    a.getComponentName().getPackageName(),
                    b.getComponentName().getPackageName());
            assertEquals(
                    a.getComponentName().getClassName(),
                    b.getComponentName().getClassName());
            assertEquals(a.getId(), b.getId());
        }
    }

    private static void assertIconEquals(Icon a, Icon b) {
        if (a != b) {
            if (a != null && b != null) {
                assertEquals(a.toString(), b.toString());
            } else {
                fail("Icons not equal: " + a + ", " + b);
            }
        }
    }

    private static void assertDefaultPhoneAccountHandleEquals(DefaultPhoneAccountHandle a,
            DefaultPhoneAccountHandle b) {
        if (a != b) {
            if (a!= null && b != null) {
                assertEquals(a.userHandle, b.userHandle);
                assertPhoneAccountHandleEquals(a.phoneAccountHandle, b.phoneAccountHandle);
            } else {
                fail("Default phone account handles are not equal: " + a + ", " + b);
            }
        }
    }

    private static void assertPhoneAccountEquals(PhoneAccount a, PhoneAccount b) {
        if (a != b) {
            if (a != null && b != null) {
                assertPhoneAccountHandleEquals(a.getAccountHandle(), b.getAccountHandle());
                assertEquals(a.getAddress(), b.getAddress());
                assertEquals(a.getSubscriptionAddress(), b.getSubscriptionAddress());
                assertEquals(a.getCapabilities(), b.getCapabilities());
                assertIconEquals(a.getIcon(), b.getIcon());
                assertEquals(a.getHighlightColor(), b.getHighlightColor());
                assertEquals(a.getLabel(), b.getLabel());
                assertEquals(a.getShortDescription(), b.getShortDescription());
                assertEquals(a.getSupportedUriSchemes(), b.getSupportedUriSchemes());
                assertBundlesEqual(a.getExtras(), b.getExtras());
                assertEquals(a.isEnabled(), b.isEnabled());
            } else {
                fail("Phone accounts not equal: " + a + ", " + b);
            }
        }
    }

    private static void assertBundlesEqual(Bundle a, Bundle b) {
        if (a == null && b == null) {
            return;
        }

        assertNotNull(a);
        assertNotNull(b);
        Set<String> keySetA = a.keySet();
        Set<String> keySetB = b.keySet();

        assertTrue("Bundle keys not the same", keySetA.containsAll(keySetB));
        assertTrue("Bundle keys not the same", keySetB.containsAll(keySetA));

        for (String keyA : keySetA) {
            assertEquals("Bundle value not the same", a.get(keyA), b.get(keyA));
        }
    }

    private static void assertStateEquals(
            PhoneAccountRegistrar.State a, PhoneAccountRegistrar.State b) {
        assertEquals(a.defaultOutgoingAccountHandles.size(),
                b.defaultOutgoingAccountHandles.size());
        for (Map.Entry<UserHandle, DefaultPhoneAccountHandle> e :
                a.defaultOutgoingAccountHandles.entrySet()) {
            assertDefaultPhoneAccountHandleEquals(e.getValue(),
                    b.defaultOutgoingAccountHandles.get(e.getKey()));
        }
        assertEquals(a.accounts.size(), b.accounts.size());
        for (int i = 0; i < a.accounts.size(); i++) {
            assertPhoneAccountEquals(a.accounts.get(i), b.accounts.get(i));
        }
    }

    private PhoneAccountRegistrar.State makeQuickStateWithTelephonyPhoneAccountHandle() {
        PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
        s.accounts.add(makeQuickAccount("id0", 0));
        s.accounts.add(makeQuickAccount("id1", 1));
        s.accounts.add(makeQuickAccount("id2", 2));
        PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(new ComponentName(
                "com.android.phone",
                        "com.android.services.telephony.TelephonyConnectionService"), "id0");
        UserHandle userHandle = phoneAccountHandle.getUserHandle();
        when(UserManager.get(mContext).getSerialNumberForUser(userHandle))
            .thenReturn(0L);
        when(UserManager.get(mContext).getUserForSerialNumber(0L))
            .thenReturn(userHandle);
        s.defaultOutgoingAccountHandles
            .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle,
                "testGroup"));
        return s;
    }

    private PhoneAccountRegistrar.State makeQuickState() {
        PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
        s.accounts.add(makeQuickAccount("id0", 0));
        s.accounts.add(makeQuickAccount("id1", 1));
        s.accounts.add(makeQuickAccount("id2", 2));
        PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(
                new ComponentName("pkg0", "cls0"), "id0");
        UserHandle userHandle = phoneAccountHandle.getUserHandle();
        when(UserManager.get(mContext).getSerialNumberForUser(userHandle))
                .thenReturn(0L);
        when(UserManager.get(mContext).getUserForSerialNumber(0L))
                .thenReturn(userHandle);
        s.defaultOutgoingAccountHandles
                .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle,
                        "testGroup"));
        return s;
    }
}
