Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e5b8b203 authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Correct issues with answer/unhold call auto-disconnect.

Telecom will occasionally need to disconnect an ongoing call either:
1. If there is a new incoming call which is answered and the current
ongoing call cannot be held.
2. There is an ongoing call which must be held so another call can be
unheld and the ongoing call cannot be held.

This does not happen often in practice, however there are some cases
involving emergency calls and the RemoteConnectionService API which could
be a problem.
Since an emergency call bypasses the RemoteConnectionService it will be
hosted directly by the TelephonyConnectionService.  Regular calls which
take place at the same time will be hosted by the connection manager via
the RemoteConnectionService API.

In the case of an ongoing e-call and a received call via a connection mgr,
answering the received call would cause the e-call to be disconnected
since it cannot be held.

There is an implicit assumption made in the code that concurrent calls
which are from the same ConnectionService don't need to be dropped in the
above answering or swapping scenarios because it is assumed that the
ConnectionService will handle that logic by itself.

The old logic to determine which calls were part of the "same connection
service" was flawed because it looks only at the ConnectionServiceWrapper
which is associated with each call.  In reality, the Call's target phone
account is a more reliable indicator.  More specifically, the package
name associated with the target phone account.  If there are concurrent
calls which target the same connection service package, the auto drop
logic should not take place.

As a final safety measure, fallbacks are added to ensure that an emergency
call will never be dropped.
In the case of answering an incoming call on a different ConnectionService
where there is an active e-call, the incoming call will instead be
rejected instead of answered.
In the case of swapping from an active e-call to a held non-ecall on a
different ConnectionService, the request is ignored rather than dropping
the e-call.

Test: Added new unit tests for these scenarios.
Test: Manual carrier calling test with simulated e-calls.
Fixes: 129505115
Change-Id: Icc272c53e9b70a9acf54c6836c0a20aa56cc4e75
parent 2fbb55d9
Loading
Loading
Loading
Loading
+32 −9
Original line number Diff line number Diff line
@@ -2152,8 +2152,17 @@ public class CallsManager extends Call.ListenerBase
                    // This call does not support hold. If it is from a different connection
                    // service, then disconnect it, otherwise invoke call.hold() and allow the
                    // connection service to handle the situation.
                    if (activeCall.getConnectionService() != call.getConnectionService()) {
                    if (!PhoneAccountHandle.areFromSamePackage(activeCall.getTargetPhoneAccount(),
                            call.getTargetPhoneAccount())) {
                        if (!activeCall.isEmergencyCall()) {
                            activeCall.disconnect("Swap to " + call.getId());
                        } else {
                            Log.w(this, "unholdCall: % is an emergency call, aborting swap to %s",
                                    activeCall.getId(), call.getId());
                            // Don't unhold the call as requested; we don't want to drop an
                            // emergency call.
                            return;
                        }
                    } else {
                        activeCall.hold("Swap to " + call.getId());
                    }
@@ -2371,7 +2380,8 @@ public class CallsManager extends Call.ListenerBase
                activeCall.hold();
                return true;
            } else if (supportsHold(activeCall)
                    && activeCall.getConnectionService() == call.getConnectionService()) {
                    && PhoneAccountHandle.areFromSamePackage(activeCall.getTargetPhoneAccount(),
                        call.getTargetPhoneAccount())) {

                // Handle the case where the active call and the new call are from the same CS, and
                // the currently active call supports hold but cannot currently be held.
@@ -2383,7 +2393,7 @@ public class CallsManager extends Call.ListenerBase
                // Call C - Incoming
                // Here we need to disconnect A prior to holding B so that C can be answered.
                // This case is driven by telephony requirements ultimately.
                Call heldCall = getHeldCallByConnectionService(call.getConnectionService());
                Call heldCall = getHeldCallByConnectionService(call.getTargetPhoneAccount());
                if (heldCall != null) {
                    heldCall.disconnect();
                    Log.i(this, "holdActiveCallForNewCall: Disconnect held call %s before "
@@ -2398,10 +2408,21 @@ public class CallsManager extends Call.ListenerBase
                // This call does not support hold. If it is from a different connection
                // service, then disconnect it, otherwise allow the connection service to
                // figure out the right states.
                if (activeCall.getConnectionService() != call.getConnectionService()) {
                if (!PhoneAccountHandle.areFromSamePackage(activeCall.getTargetPhoneAccount(),
                        call.getTargetPhoneAccount())) {
                    Log.i(this, "holdActiveCallForNewCall: disconnecting %s so that %s can be "
                            + "made active.", activeCall.getId(), call.getId());
                    if (!activeCall.isEmergencyCall()) {
                        activeCall.disconnect();
                    } else {
                        // It's not possible to hold the active call, and its an emergency call so
                        // we will silently reject the incoming call instead of answering it.
                        Log.w(this, "holdActiveCallForNewCall: rejecting incoming call %s as "
                                + "the active call is an emergency call and it cannot be held.",
                                call.getId());
                        call.reject(false /* rejectWithMessage */, "" /* message */,
                                "active emergency call can't be held");
                    }
                }
            }
        }
@@ -2672,9 +2693,10 @@ public class CallsManager extends Call.ListenerBase
        return getFirstCallWithState(CallState.ON_HOLD);
    }

    public Call getHeldCallByConnectionService(ConnectionServiceWrapper connSvr) {
    public Call getHeldCallByConnectionService(PhoneAccountHandle targetPhoneAccount) {
        Optional<Call> heldCall = mCalls.stream()
                .filter(call -> call.getConnectionService() == connSvr
                .filter(call -> PhoneAccountHandle.areFromSamePackage(call.getTargetPhoneAccount(),
                        targetPhoneAccount)
                        && call.getParentCall() == null
                        && call.getState() == CallState.ON_HOLD)
                .findFirst();
@@ -3392,7 +3414,8 @@ public class CallsManager extends Call.ListenerBase
            // First thing, if we are trying to make a call with the same phone account as the live
            // call, then allow it so that the connection service can make its own decision about
            // how to handle the new call relative to the current one.
            if (Objects.equals(liveCallPhoneAccount, call.getTargetPhoneAccount())) {
            if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
                    call.getTargetPhoneAccount())) {
                Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
                call.getAnalytics().setCallIsAdditional(true);
                liveCall.getAnalytics().setCallIsInterrupted(true);
+74 −51
Original line number Diff line number Diff line
@@ -56,14 +56,12 @@ import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceFocusManager;
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.HeadsetMediaButton;
@@ -114,6 +112,8 @@ public class CallsManagerTest extends TelecomTestCase {
            ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
    private static final PhoneAccountHandle SIM_2_HANDLE = new PhoneAccountHandle(
            ComponentName.unflattenFromString("com.foo/.Blah"), "Sim2");
    private static final PhoneAccountHandle VOIP_1_HANDLE = new PhoneAccountHandle(
            ComponentName.unflattenFromString("com.voip/.Stuff"), "Voip1");
    private static final PhoneAccountHandle SELF_MANAGED_HANDLE = new PhoneAccountHandle(
            ComponentName.unflattenFromString("com.foo/.Self"), "Self");
    private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
@@ -141,6 +141,7 @@ public class CallsManagerTest extends TelecomTestCase {
                put(TEST_ADDRESS3, SIM_2_HANDLE);
    }};

    private static int sCallId = 1;
    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
    @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
    @Mock private MissedCallNotifier mMissedCallNotifier;
@@ -523,17 +524,14 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testUnholdCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
        ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // and a held call which has different ConnectionService
        Call heldCall = addSpyCallWithConnectionService(connSvr2);
        Call heldCall = addSpyCall(VOIP_1_HANDLE);

        // WHEN unhold the held call
        mCallsManager.unholdCall(heldCall);
@@ -548,17 +546,39 @@ public class CallsManagerTest extends TelecomTestCase {

    @SmallTest
    @Test
    public void testUnholdCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
        ConnectionServiceWrapper connSvr = Mockito.mock(ConnectionServiceWrapper.class);
    public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
        // GIVEN a CallsManager with ongoing call, and this call can not be held, but it also an
        // emergency call.
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        doReturn(true).when(ongoingCall).isEmergencyCall();
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // and a held call which has different ConnectionService
        Call heldCall = addSpyCall(VOIP_1_HANDLE);

        // WHEN unhold the held call
        mCallsManager.unholdCall(heldCall);

        // THEN the ongoing call will not be disconnected (because its an emergency call)
        verify(ongoingCall, never()).disconnect(any());

        // and held call is not un-held
        verify(heldCall, never()).unhold(any());
    }

    @SmallTest
    @Test
    public void testUnholdCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCallWithConnectionService(connSvr);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // and a held call which has different ConnectionService
        Call heldCall = addSpyCallWithConnectionService(connSvr);
        // and a held call which has the same ConnectionService
        Call heldCall = addSpyCall(SIM_2_HANDLE);

        // WHEN unhold the held call
        mCallsManager.unholdCall(heldCall);
@@ -595,15 +615,13 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testAnswerCallWhenOngoingHasSameConnectionService() {
        ConnectionServiceWrapper connSvr = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCallWithConnectionService(connSvr);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // WHEN answer an incoming call
        Call incomingCall = addSpyCallWithConnectionService(connSvr);
        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);

        // THEN nothing happened on the ongoing call and the focus request for incoming call is sent
@@ -616,17 +634,14 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testAnswerCallWhenOngoingHasDifferentConnectionService() {
        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
        ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // WHEN answer an incoming call
        Call incomingCall = addSpyCallWithConnectionService(connSvr2);
        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);

        // THEN the ongoing call is disconnected and the focus request for incoming call is sent
@@ -639,27 +654,46 @@ public class CallsManagerTest extends TelecomTestCase {

    @SmallTest
    @Test
    public void testAnswerCallWhenMultipleHeldCallsExisted() {
        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
        ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);
    public void testAnswerCallWhenOngoingHasDifferentConnectionServiceButIsEmerg() {
        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        doReturn(true).when(ongoingCall).isEmergencyCall();
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // WHEN answer an incoming call
        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);

        // THEN the ongoing call is not disconnected
        verify(ongoingCall, never()).disconnect();

        // and the incoming call is not answered, but is rejected instead.
        verify(incomingCall, never()).answer(VideoProfile.STATE_AUDIO_ONLY);
        verify(incomingCall).reject(eq(false), any(), any());
    }

    @SmallTest
    @Test
    public void testAnswerCallWhenMultipleHeldCallsExisted() {
        // Given an ongoing call and held call with the ConnectionService connSvr1. The
        // ConnectionService connSvr1 can handle one held call
        Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        Call heldCall = addSpyCallWithConnectionService(connSvr1);
        Call heldCall = addSpyCall(SIM_1_HANDLE);
        doReturn(CallState.ON_HOLD).when(heldCall).getState();

        // and other held call has difference ConnectionService
        Call heldCall2 = addSpyCallWithConnectionService(connSvr2);
        Call heldCall2 = addSpyCall(VOIP_1_HANDLE);
        doReturn(CallState.ON_HOLD).when(heldCall2).getState();

        // WHEN answer an incoming call which ConnectionService is connSvr1
        Call incomingCall = addSpyCallWithConnectionService(connSvr1);
        Call incomingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(true).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);

@@ -717,17 +751,14 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
        ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        doReturn(ongoingCall).when(mConnectionSvrFocusMgr).getCurrentFocusCall();

        // and a new self-managed call which has different ConnectionService
        Call newCall = addSpyCallWithConnectionService(connSvr2);
        Call newCall = addSpyCall(VOIP_1_HANDLE);
        doReturn(true).when(newCall).isSelfManaged();

        // WHEN active the new call
@@ -744,16 +775,14 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
        ConnectionServiceWrapper connSvr = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN a CallsManager with ongoing call, and this call can not be held
        Call ongoingCall = addSpyCallWithConnectionService(connSvr);
        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);

        // and a new self-managed call which has the same ConnectionService
        Call newCall = addSpyCallWithConnectionService(connSvr);
        Call newCall = addSpyCall(SIM_1_HANDLE);
        doReturn(true).when(newCall).isSelfManaged();

        // WHEN active the new call
@@ -794,10 +823,8 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testNoFilteringOfSelfManagedCalls() {
        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN an incoming call which is self managed.
        Call incomingCall = addSpyCallWithConnectionService(connSvr1);
        Call incomingCall = addSpyCall(SELF_MANAGED_HANDLE);
        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
        doReturn(true).when(incomingCall).isSelfManaged();
@@ -913,10 +940,8 @@ public class CallsManagerTest extends TelecomTestCase {
    @SmallTest
    @Test
    public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);

        // GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
        Call incomingCall = addSpyCallWithConnectionService(connSvr1);
        Call incomingCall = addSpyCall(SIM_1_HANDLE);
        Bundle extras = new Bundle();
        extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
        PhoneAccount skipRequestedAccount = new PhoneAccount.Builder(SIM_2_HANDLE, "Skipper")
@@ -925,7 +950,7 @@ public class CallsManagerTest extends TelecomTestCase {
            .setExtras(extras)
            .setIsEnabled(true)
            .build();
        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_2_HANDLE))
        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
            .thenReturn(skipRequestedAccount);
        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
@@ -1026,14 +1051,12 @@ public class CallsManagerTest extends TelecomTestCase {
                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
    }

    private Call addSpyCallWithConnectionService(ConnectionServiceWrapper connSvr) {
        Call call = addSpyCall();
        doReturn(connSvr).when(call).getConnectionService();
        return call;
    private Call addSpyCall() {
        return addSpyCall(SIM_2_HANDLE);
    }

    private Call addSpyCall() {
        Call ongoingCall = new Call("1", /* callId */
    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount) {
        Call ongoingCall = new Call(String.format("TC@%d", sCallId++), /* callId */
                mComponentContextFixture.getTestDouble(),
                mCallsManager,
                mLock, /* ConnectionServiceRepository */
@@ -1042,7 +1065,7 @@ public class CallsManagerTest extends TelecomTestCase {
                TEST_ADDRESS,
                null /* GatewayInfo */,
                null /* connectionManagerPhoneAccountHandle */,
                SIM_2_HANDLE,
                targetPhoneAccount,
                Call.CALL_DIRECTION_INCOMING,
                false /* shouldAttachToExistingConnection*/,
                false /* isConference */,
+1 −1
Original line number Diff line number Diff line
@@ -440,7 +440,7 @@ public class ComponentContextFixture implements TestFixture<Context> {
    private final ApplicationInfo mTestApplicationInfo = new ApplicationInfo();
    // private final RoleManager mRoleManager = mock(RoleManager.class);

    private TelecomManager mTelecomManager = null;
    private TelecomManager mTelecomManager = mock(TelecomManager.class);

    public ComponentContextFixture() {
        MockitoAnnotations.initMocks(this);
+32 −0
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.telecom.Log;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Xml;

@@ -910,6 +911,37 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase {
        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));
    }

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