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

Commit 63a5768e authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Fix issue with late binding to ICS.

If a call is added to InCallController and NO bindings take place, Telecom
would not track that call in its call id mapper.  If one of the disabled
ICS becomes enabled later, Telecom would skip binding because it assumes
it should only have bound if there is a call present.

To fix this, moved the "addCall" invocation to happen all the time in
onCallAdded; this ensures that regardless of whether we bind to an ICS we
still know about the call if a package enables later on in the call.

Test: Added failing unit test and verified that code change fixes it.
Test: Manual test by modifying Telecom so that it doesn't do an initial
binding; verified that I was able to repro the original bug and that the
change fixed it.
Fixes: 280309857

Change-Id: I8783b2ce5be4af9e24d6ffa4bca81b23a64e0700
parent 2ab0678e
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -1191,6 +1191,11 @@ public class InCallController extends CallsManagerListenerBase implements
    @Override
    public void onCallAdded(Call call) {
        UserHandle userFromCall = getUserFromCall(call);

        Log.i(this, "onCallAdded: %s", call);
        // Track the call if we don't already know about it.
        addCall(call);

        if (!isBoundAndConnectedToServices(userFromCall)) {
            Log.i(this, "onCallAdded: %s; not bound or connected.", call);
            // We are not bound, or we're not connected.
@@ -1206,10 +1211,6 @@ public class InCallController extends CallsManagerListenerBase implements
            mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
                    userFromCall);

            Log.i(this, "onCallAdded: %s", call);
            // Track the call if we don't already know about it.
            addCall(call);

            if (inCallServiceConnection != null) {
                Log.i(this, "mInCallServiceConnection isConnected=%b",
                        inCallServiceConnection.isConnected());
+101 −0
Original line number Diff line number Diff line
@@ -1001,6 +1001,107 @@ public class InCallControllerTests extends TelecomTestCase {
        verify(mMockContext, times(2)).unbindService(any(ServiceConnection.class));
    }

    /**
     * Tests a case where InCallController DOES NOT bind to ANY InCallServices when the call is
     * first added, but then one becomes available after the call starts.  This test was originally
     * added to reproduce a bug which would cause the call id mapper in the InCallController to not
     * track a newly added call unless something was bound when the call was first added.
     * @throws Exception
     */
    @MediumTest
    @Test
    public void testNoInitialBinding() throws Exception {
        Bundle callExtras = new Bundle();
        callExtras.putBoolean("whatever", true);

        // Make a basic call
        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
        when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
        when(mMockCall.isEmergencyCall()).thenReturn(true);
        when(mMockContext.getSystemService(eq(UserManager.class)))
                .thenReturn(mMockUserManager);
        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
        when(mMockCall.isIncoming()).thenReturn(false);
        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
        when(mMockCall.isExternalCall()).thenReturn(false);
        when(mMockCall.isSelfManaged()).thenReturn(true);
        when(mMockCall.visibleToInCallService()).thenReturn(true);

        // Dialer doesn't handle these calls, but non-UI ICS does.
        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
                .thenReturn(DEF_PKG);
        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
                ArgumentCaptor.forClass(ServiceConnection.class);
        when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
                eq(serviceBindingFlags),
                eq(mUserHandle))).thenReturn(true);
        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                .thenReturn(300_000L);

        // Setup package manager; there is a dialer and disable non-ui ICS
        when(mMockPackageManager.queryIntentServicesAsUser(
                any(Intent.class), anyInt(), anyInt())).thenReturn(
                Arrays.asList(
                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
                        getNonUiResolveinfo(true /* selfManaged */,
                                false /* isEnabled */)
                )
        );
        when(mMockPackageManager
                .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
        when(mMockPackageManager
                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);

        // Add the call.
        mInCallController.onCallAdded(mMockCall);

        // There will be 4 calls for the various types of ICS; this is normal.
        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
                any(Intent.class),
                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
                eq(CURRENT_USER_ID));

        // Verify no bind at this point
        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
        verify(mMockContext, never()).bindServiceAsUser(
                bindIntentCaptor.capture(),
                any(ServiceConnection.class),
                eq(serviceBindingFlags),
                eq(mUserHandle));

        // Setup mocks to enable non-ui ICS
        when(mMockPackageManager.queryIntentServicesAsUser(
                any(Intent.class), anyInt(), anyInt())).thenReturn(
                Arrays.asList(
                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
                        getNonUiResolveinfo(true /* selfManaged */,
                                true /* isEnabled */)
                )
        );
        when(mMockPackageManager
                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);

        // Emulate a late enable of the non-ui ICS
        Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
        packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
        packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
                new String[] {NONUI_CLASS});
        packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
        mRegisteredReceiver.onReceive(mMockContext, packageUpdated);

        // Make sure we bound to it.
        verify(mMockContext, times(1)).bindServiceAsUser(
                bindIntentCaptor.capture(),
                any(ServiceConnection.class),
                eq(serviceBindingFlags),
                eq(mUserHandle));
    }

    /**
     * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
     * supports external calls.