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

Commit b9f32ef8 authored by Hall Liu's avatar Hall Liu Committed by Gerrit Code Review
Browse files

Merge "Remove call only after the incall service is bound"

parents ca75f42f 0dac328a
Loading
Loading
Loading
Loading
+25 −23
Original line number Diff line number Diff line
@@ -2360,6 +2360,7 @@ public class CallsManager extends Call.ListenerBase
     * Removes an existing disconnected call, and notifies the in-call app.
     */
    void markCallAsRemoved(Call call) {
        mInCallController.getBindingFuture().thenRunAsync(() -> {
            call.maybeCleanupHandover();
            removeCall(call);
            Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
@@ -2369,8 +2370,8 @@ public class CallsManager extends Call.ListenerBase
                        + isDisconnectingChildCall + "call -> %s", call);
                mLocallyDisconnectingCalls.remove(call);
                // Auto-unhold the foreground call due to a locally disconnected call, except if the
            // call which was disconnected is a member of a conference (don't want to auto un-hold
            // the conference if we remove a member of the conference).
                // call which was disconnected is a member of a conference (don't want to auto
                // un-hold the conference if we remove a member of the conference).
                if (!isDisconnectingChildCall && foregroundCall != null
                        && foregroundCall.getState() == CallState.ON_HOLD) {
                    foregroundCall.unhold();
@@ -2380,11 +2381,12 @@ public class CallsManager extends Call.ListenerBase
                    foregroundCall.getState() == CallState.ON_HOLD) {

                // The new foreground call is on hold, however the carrier does not display the hold
            // button in the UI.  Therefore, we need to auto unhold the held call since the user has
            // no means of unholding it themselves.
                // button in the UI.  Therefore, we need to auto unhold the held call since the user
                // has no means of unholding it themselves.
                Log.i(this, "Auto-unholding held foreground call (call doesn't support hold)");
                foregroundCall.unhold();
            }
        }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock));
    }

    /**
+19 −0
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -736,6 +738,10 @@ public class InCallController extends CallsManagerListenerBase {
    private CarSwappingInCallServiceConnection mInCallServiceConnection;
    private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;

    // Future that's in a completed state unless we're in the middle of binding to a service.
    // The future will complete with true if binding succeeds, false if it timed out.
    private CompletableFuture<Boolean> mBindingFuture = CompletableFuture.completedFuture(true);

    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
            SystemStateHelper systemStateHelper,
            DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
@@ -1134,6 +1140,10 @@ public class InCallController extends CallsManagerListenerBase {
            // Only connect to the non-ui InCallServices if we actually connected to the main UI
            // one.
            connectToNonUiInCallServices(call);
            mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
                    mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                            mContext.getContentResolver()),
                    TimeUnit.MILLISECONDS);
        } else {
            Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
        }
@@ -1399,6 +1409,7 @@ public class InCallController extends CallsManagerListenerBase {
            inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
        } catch (RemoteException ignored) {
        }
        mBindingFuture.complete(true);
        Log.i(this, "%s calls sent to InCallService.", numCallsSent);
        Trace.endSection();
        return true;
@@ -1485,6 +1496,14 @@ public class InCallController extends CallsManagerListenerBase {
        return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
    }

    /**
     * @return A future that is pending whenever we are in the middle of binding to an
     *         incall service.
     */
    public CompletableFuture<Boolean> getBindingFuture() {
        return mBindingFuture;
    }

    /**
     * Dumps the state of the {@link InCallController}.
     *
+54 −1
Original line number Diff line number Diff line
@@ -70,10 +70,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.any;
@@ -608,6 +610,57 @@ public class InCallControllerTests extends TelecomTestCase {
        assertEquals(CAR_CLASS, bindIntent.getComponent().getClassName());
    }

    /**
     * Make sure the InCallController completes its binding future when the in call service
     * finishes binding.
     */
    @MediumTest
    @Test
    public void testBindingFuture() throws Exception {
        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
        when(mMockCall.isIncoming()).thenReturn(false);
        when(mMockCall.isExternalCall()).thenReturn(false);
        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
        when(mMockContext.bindServiceAsUser(nullable(Intent.class),
                nullable(ServiceConnection.class), anyInt(), nullable(UserHandle.class)))
                .thenReturn(true);
        when(mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                nullable(ContentResolver.class))).thenReturn(500L);

        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
        mInCallController.bindToServices(mMockCall);

        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
                ArgumentCaptor.forClass(ServiceConnection.class);
        verify(mMockContext, times(1)).bindServiceAsUser(
                bindIntentCaptor.capture(),
                serviceConnectionCaptor.capture(),
                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
                eq(UserHandle.CURRENT));

        CompletableFuture<Boolean> bindTimeout = mInCallController.getBindingFuture();

        assertFalse(bindTimeout.isDone());

        // Start the connection, make sure we don't unbind, and make sure that we don't send
        // anything to the in-call service yet.
        ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
        ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
        IBinder mockBinder = mock(IBinder.class);
        IInCallService mockInCallService = mock(IInCallService.class);
        when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockInCallService);

        serviceConnection.onServiceConnected(defDialerComponentName, mockBinder);
        verify(mockInCallService).setInCallAdapter(nullable(IInCallAdapter.class));

        // Make sure that the future completed without timing out.
        assertTrue(bindTimeout.getNow(false));
    }

    private void setupMocks(boolean isExternalCall) {
        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);