Loading flags/telecom_api_flags.aconfig +8 −1 Original line number Diff line number Diff line Loading @@ -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" } src/com/android/server/telecom/ConnectionServiceWrapper.java +47 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading @@ -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 { Loading Loading @@ -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(), Loading @@ -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( Loading Loading @@ -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; Loading Loading @@ -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(), Loading @@ -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( Loading Loading @@ -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); } Loading Loading @@ -2629,4 +2665,9 @@ public class ConnectionServiceWrapper extends ServiceBinder implements sb.append("]"); return sb.toString(); } @VisibleForTesting public void setScheduledExecutorService(ScheduledExecutorService service) { mScheduledExecutor = service; } } tests/src/com/android/server/telecom/tests/BasicCallTests.java +2 −0 Original line number Diff line number Diff line Loading @@ -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); } /** Loading @@ -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); } /** Loading tests/src/com/android/server/telecom/tests/CallsManagerTest.java +61 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(); } Loading Loading @@ -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() { Loading Loading @@ -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(); } } Loading
flags/telecom_api_flags.aconfig +8 −1 Original line number Diff line number Diff line Loading @@ -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" }
src/com/android/server/telecom/ConnectionServiceWrapper.java +47 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading @@ -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 { Loading Loading @@ -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(), Loading @@ -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( Loading Loading @@ -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; Loading Loading @@ -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(), Loading @@ -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( Loading Loading @@ -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); } Loading Loading @@ -2629,4 +2665,9 @@ public class ConnectionServiceWrapper extends ServiceBinder implements sb.append("]"); return sb.toString(); } @VisibleForTesting public void setScheduledExecutorService(ScheduledExecutorService service) { mScheduledExecutor = service; } }
tests/src/com/android/server/telecom/tests/BasicCallTests.java +2 −0 Original line number Diff line number Diff line Loading @@ -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); } /** Loading @@ -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); } /** Loading
tests/src/com/android/server/telecom/tests/CallsManagerTest.java +61 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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(); } Loading Loading @@ -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() { Loading Loading @@ -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(); } }