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

Commit c6048375 authored by Grace Jia's avatar Grace Jia Committed by xiaotonj
Browse files

Unbind ConnectionService if connection creation timed out.

Currently we won't check if the connection creation can complete in a
reasonable time after we bind to that ConnectionService. This may allow
malicious app keep its declared permission forever even if it is in
background. Solve this by manually unbind the ConnectionService if the
connection/conference creation can't complete in 15 seconds.

Bug: b/293458004
Test: Manually test using BugFinder app provided in the bug
Change-Id: I0d17564d1977b61770cf7e4a46a8ee353b8b42e8
parent 49d60220
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -13,3 +13,10 @@ flag {
  description: "When set, call details/extras id updates to Telecom APIs for Android V are active."
  bug: "301713560"
}

flag {
  name: "unbind_timeout_connections"
  namespace: "telecom"
  description: "When set, Telecom will auto-unbind if a ConnectionService returns no connections after some time."
  bug: "293458004"
}
+47 −6
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
@@ -65,6 +64,7 @@ import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.RemoteServiceCallback;
import com.android.internal.util.Preconditions;
import com.android.server.telecom.flags.Flags;

import java.util.ArrayList;
import java.util.Collection;
@@ -72,13 +72,14 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.Objects;

/**
 * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -91,9 +92,12 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
        ConnectionServiceFocusManager.ConnectionServiceFocus {

    private static final String TELECOM_ABBREVIATION = "cast";
    private static final long SERVICE_BINDING_TIMEOUT = 15000L;
    private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
    private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
    private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
    private ScheduledExecutorService mScheduledExecutor =
            Executors.newSingleThreadScheduledExecutor();

    private final class Adapter extends IConnectionServiceAdapter.Stub {

@@ -1600,7 +1604,22 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                        .setParticipants(call.getParticipants())
                        .setIsAdhocConferenceCall(call.isAdhocConferenceCall())
                        .build();

                if (Flags.unbindTimeoutConnections()) {
                    android.telecom.Logging.Runnable r =
                            new android.telecom.Logging.Runnable("CSW.cC", mLock) {
                        @Override
                        public void loggedRun() {
                            if (!call.isCreateConnectionComplete()) {
                                Log.e(this, new Exception(), "Conference %s creation timeout",
                                        getComponentName());
                                response.handleCreateConferenceFailure(
                                        new DisconnectCause(DisconnectCause.ERROR));
                            }
                        }
                    };
                    mScheduledExecutor.schedule(r.getRunnableToCancel(), SERVICE_BINDING_TIMEOUT,
                            TimeUnit.MILLISECONDS);
                }
                try {
                    mServiceInterface.createConference(
                            call.getConnectionManagerPhoneAccount(),
@@ -1609,7 +1628,6 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                            call.shouldAttachToExistingConnection(),
                            call.isUnknown(),
                            Log.getExternalSession(TELECOM_ABBREVIATION));

                } catch (RemoteException e) {
                    Log.e(this, e, "Failure to createConference -- %s", getComponentName());
                    mPendingResponses.remove(callId).handleCreateConferenceFailure(
@@ -1642,6 +1660,7 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                    Log.i(ConnectionServiceWrapper.this, "Call not present"
                            + " in call id mapper, maybe it was aborted before the bind"
                            + " completed successfully?");

                    response.handleCreateConnectionFailure(
                            new DisconnectCause(DisconnectCause.CANCELED));
                    return;
@@ -1703,6 +1722,23 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                        .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
                        .build();

                if (Flags.unbindTimeoutConnections()) {
                    android.telecom.Logging.Runnable r =
                            new android.telecom.Logging.Runnable("CSW.cC", mLock) {
                                @Override
                                public void loggedRun() {
                                    if (!call.isCreateConnectionComplete()) {
                                        Log.e(this, new Exception(),
                                                "Connection %s creation timeout",
                                                getComponentName());
                                        response.handleCreateConnectionFailure(
                                                new DisconnectCause(DisconnectCause.ERROR));
                                    }
                                }
                            };
                    mScheduledExecutor.schedule(r.getRunnableToCancel(), SERVICE_BINDING_TIMEOUT,
                            TimeUnit.MILLISECONDS);
                }
                try {
                    mServiceInterface.createConnection(
                            call.getConnectionManagerPhoneAccount(),
@@ -1711,7 +1747,6 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
                            call.shouldAttachToExistingConnection(),
                            call.isUnknown(),
                            Log.getExternalSession(TELECOM_ABBREVIATION));

                } catch (RemoteException e) {
                    Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                    mPendingResponses.remove(callId).handleCreateConnectionFailure(
@@ -2160,7 +2195,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
        }
    }

    void addCall(Call call) {
    @VisibleForTesting
    public void addCall(Call call) {
        if (mCallIdMapper.getCallId(call) == null) {
            mCallIdMapper.addCall(call);
        }
@@ -2629,4 +2665,9 @@ public class ConnectionServiceWrapper extends ServiceBinder implements
        sb.append("]");
        return sb.toString();
    }

    @VisibleForTesting
    public void setScheduledExecutorService(ScheduledExecutorService service) {
        mScheduledExecutor = service;
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -997,6 +997,7 @@ public class BasicCallTests extends TelecomSystemTest {
        call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
        assert(call.isVideoCallingSupportedByPhoneAccount());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
        call.setIsCreateConnectionComplete(true);
    }

    /**
@@ -1020,6 +1021,7 @@ public class BasicCallTests extends TelecomSystemTest {
        call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
        assert(!call.isVideoCallingSupportedByPhoneAccount());
        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
        call.setIsCreateConnectionComplete(true);
    }

    /**
+61 −4
Original line number Diff line number Diff line
@@ -17,10 +17,8 @@
package com.android.server.telecom.tests;

import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;

import static junit.framework.Assert.assertNotNull;
import static junit.framework.TestCase.fail;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -43,6 +41,7 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.lang.Thread.sleep;

import android.Manifest;
import android.content.ComponentName;
@@ -54,6 +53,7 @@ import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.Process;
@@ -61,6 +61,7 @@ import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.BlockedNumberContract;
import android.telecom.CallException;
import android.telecom.CallScreeningService;
@@ -80,6 +81,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import android.util.Pair;
import android.widget.Toast;

import com.android.internal.telecom.IConnectionService;
import com.android.server.telecom.AnomalyReporterAdapter;
import com.android.server.telecom.AsyncRingtonePlayer;
import com.android.server.telecom.Call;
@@ -98,6 +100,7 @@ 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.CreateConnectionResponse;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.EmergencyCallHelper;
@@ -124,8 +127,9 @@ import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
import com.android.server.telecom.flags.FeatureFlags;
import com.android.server.telecom.flags.Flags;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallStreamingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
@@ -134,6 +138,7 @@ import com.android.server.telecom.voip.TransactionManager;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -284,7 +289,8 @@ public class CallsManagerTest extends TelecomTestCase {
    @Mock private FeatureFlags mFeatureFlags;

    @Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;

    @Mock private IConnectionService mIConnectionService;
    @Rule public SetFlagsRule mSetRlagsRule = new SetFlagsRule();
    private CallsManager mCallsManager;

    @Override
@@ -372,11 +378,17 @@ public class CallsManagerTest extends TelecomTestCase {
                eq(WORK_HANDLE), any())).thenReturn(WORK_ACCOUNT);
        when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
        when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
        when(mIConnectionService.asBinder()).thenReturn(mock(IBinder.class));

        mComponentContextFixture.addConnectionService(
                SIM_1_ACCOUNT.getAccountHandle().getComponentName(), mIConnectionService);
    }

    @Override
    @After
    public void tearDown() throws Exception {
        mComponentContextFixture.removeConnectionService(
                SIM_1_ACCOUNT.getAccountHandle().getComponentName(), mIConnectionService);
        super.tearDown();
    }

@@ -2822,6 +2834,36 @@ public class CallsManagerTest extends TelecomTestCase {
        assertTrue(result.contains("onReceiveResult"));
    }

    @Test
    public void testConnectionServiceCreateConnectionTimeout() throws Exception {
        mSetRlagsRule.enableFlags(Flags.FLAG_UNBIND_TIMEOUT_CONNECTIONS);
        ConnectionServiceWrapper service = new ConnectionServiceWrapper(
                SIM_1_ACCOUNT.getAccountHandle().getComponentName(), null,
                mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
        TestScheduledExecutorService scheduledExecutorService = new TestScheduledExecutorService();
        service.setScheduledExecutorService(scheduledExecutorService);
        Call call = addSpyCall();
        service.addCall(call);
        when(call.isCreateConnectionComplete()).thenReturn(false);
        CreateConnectionResponse response = mock(CreateConnectionResponse.class);

        service.createConnection(call, response);
        waitUntilConditionIsTrueOrTimeout(new Condition() {
            @Override
            public Object expected() {
                return true;
            }

            @Override
            public Object actual() {
                return scheduledExecutorService.isRunnableScheduledAtTime(15000L);
            }
        }, 5000L, "Expected job failed to schedule");
        scheduledExecutorService.advanceTime(15000L);
        verify(response).handleCreateConnectionFailure(
                eq(new DisconnectCause(DisconnectCause.ERROR)));
    }

    @SmallTest
    @Test
    public void testOnFailedOutgoingCallUnholdsCallAfterLocallyDisconnect() {
@@ -3417,4 +3459,19 @@ public class CallsManagerTest extends TelecomTestCase {
        when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
        when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
    }

    private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
                                                   String description) throws InterruptedException {
        final long start = System.currentTimeMillis();
        while (!condition.expected().equals(condition.actual())
                && System.currentTimeMillis() - start < timeout) {
            sleep(50);
        }
        assertEquals(description, condition.expected(), condition.actual());
    }

    protected interface Condition {
        Object expected();
        Object actual();
    }
}