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

Commit c07cb0cf authored by Hall Liu's avatar Hall Liu Committed by Android (Google) Code Review
Browse files

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

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


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


    /**
    /**
+19 −0
Original line number Original line Diff line number Diff line
@@ -54,6 +54,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
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
 * 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 CarSwappingInCallServiceConnection mInCallServiceConnection;
    private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
    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,
    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
            SystemStateHelper systemStateHelper,
            SystemStateHelper systemStateHelper,
            DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
            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
            // Only connect to the non-ui InCallServices if we actually connected to the main UI
            // one.
            // one.
            connectToNonUiInCallServices(call);
            connectToNonUiInCallServices(call);
            mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
                    mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                            mContext.getContentResolver()),
                    TimeUnit.MILLISECONDS);
        } else {
        } else {
            Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
            Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
        }
        }
@@ -1401,6 +1411,7 @@ public class InCallController extends CallsManagerListenerBase {
            inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
            inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
        } catch (RemoteException ignored) {
        } catch (RemoteException ignored) {
        }
        }
        mBindingFuture.complete(true);
        Log.i(this, "%s calls sent to InCallService.", numCallsSent);
        Log.i(this, "%s calls sent to InCallService.", numCallsSent);
        Trace.endSection();
        Trace.endSection();
        return true;
        return true;
@@ -1487,6 +1498,14 @@ public class InCallController extends CallsManagerListenerBase {
        return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
        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}.
     * Dumps the state of the {@link InCallController}.
     *
     *
+70 −0
Original line number Original line Diff line number Diff line
@@ -86,6 +86,25 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.LinkedList;
import java.util.LinkedList;
import java.util.List;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static org.junit.Assert.assertEquals;
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;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;


@RunWith(JUnit4.class)
@RunWith(JUnit4.class)
public class InCallControllerTests extends TelecomTestCase {
public class InCallControllerTests extends TelecomTestCase {
@@ -654,6 +673,57 @@ public class InCallControllerTests extends TelecomTestCase {
        assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
        assertEquals(DEF_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) {
    private void setupMocks(boolean isExternalCall) {
        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);