| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.telecom.tests; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.atLeastOnce; |
| 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.verify; |
| import static org.mockito.Mockito.isA; |
| import static org.junit.Assert.assertEquals; |
| import static org.mockito.ArgumentMatchers.nullable; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.OutcomeReceiver; |
| import android.os.UserHandle; |
| import android.telecom.CallAttributes; |
| import android.telecom.DisconnectCause; |
| import android.telecom.PhoneAccountHandle; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.server.telecom.Call; |
| import com.android.server.telecom.CallState; |
| import com.android.server.telecom.CallerInfoLookupHelper; |
| import com.android.server.telecom.CallsManager; |
| import com.android.server.telecom.ClockProxy; |
| import com.android.server.telecom.ConnectionServiceWrapper; |
| import com.android.server.telecom.flags.FeatureFlags; |
| import com.android.server.telecom.PhoneNumberUtilsAdapter; |
| import com.android.server.telecom.TelecomSystem; |
| import com.android.server.telecom.ui.ToastFactory; |
| import com.android.server.telecom.voip.EndCallTransaction; |
| import com.android.server.telecom.voip.HoldCallTransaction; |
| import com.android.server.telecom.voip.IncomingCallTransaction; |
| import com.android.server.telecom.voip.OutgoingCallTransaction; |
| import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction; |
| import com.android.server.telecom.voip.RequestNewActiveCallTransaction; |
| import com.android.server.telecom.voip.VerifyCallStateChangeTransaction; |
| import com.android.server.telecom.voip.VoipCallTransactionResult; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| |
| public class TransactionTests extends TelecomTestCase { |
| |
| private static final String CALL_ID_1 = "1"; |
| |
| private static final PhoneAccountHandle mHandle = new PhoneAccountHandle( |
| new ComponentName("foo", "bar"), "1"); |
| private static final String TEST_NAME = "Sergey Brin"; |
| private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123"); |
| |
| @Mock private Call mMockCall1; |
| @Mock private Context mMockContext; |
| @Mock private CallsManager mCallsManager; |
| @Mock private ToastFactory mToastFactory; |
| @Mock private ClockProxy mClockProxy; |
| @Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter; |
| @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper; |
| |
| private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { |
| }; |
| private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212"); |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| MockitoAnnotations.initMocks(this); |
| Mockito.when(mMockCall1.getId()).thenReturn(CALL_ID_1); |
| } |
| |
| @Override |
| @After |
| public void tearDown() throws Exception { |
| super.tearDown(); |
| } |
| |
| @Test |
| public void testEndCallTransactionWithDisconnect() throws Exception { |
| // GIVEN |
| EndCallTransaction transaction = |
| new EndCallTransaction(mCallsManager, new DisconnectCause(0), mMockCall1); |
| |
| // WHEN |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .markCallAsDisconnected(mMockCall1, new DisconnectCause(0)); |
| verify(mCallsManager, never()) |
| .rejectCall(mMockCall1, 0); |
| verify(mCallsManager, times(1)) |
| .markCallAsRemoved(mMockCall1); |
| } |
| |
| @Test |
| public void testHoldCallTransaction() throws Exception { |
| // GIVEN |
| Call spyCall = createSpyCall(null, CallState.ACTIVE, CALL_ID_1); |
| |
| HoldCallTransaction transaction = |
| new HoldCallTransaction(mCallsManager, spyCall); |
| |
| // WHEN |
| when(mCallsManager.canHold(spyCall)).thenReturn(true); |
| doAnswer(invocation -> { |
| Call call = invocation.getArgument(0); |
| call.setState(CallState.ON_HOLD, "manual set"); |
| return null; |
| }).when(mCallsManager).markCallAsOnHold(spyCall); |
| |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .markCallAsOnHold(spyCall); |
| |
| assertEquals(CallState.ON_HOLD, spyCall.getState()); |
| } |
| |
| @Test |
| public void testRequestNewCallFocusWithDialingCall() throws Exception { |
| // GIVEN |
| RequestNewActiveCallTransaction transaction = |
| new RequestNewActiveCallTransaction(mCallsManager, mMockCall1); |
| |
| // WHEN |
| when(mMockCall1.getState()).thenReturn(CallState.DIALING); |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class)); |
| } |
| |
| @Test |
| public void testRequestNewCallFocusWithRingingCall() throws Exception { |
| // GIVEN |
| RequestNewActiveCallTransaction transaction = |
| new RequestNewActiveCallTransaction(mCallsManager, mMockCall1); |
| |
| // WHEN |
| when(mMockCall1.getState()).thenReturn(CallState.RINGING); |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class)); |
| } |
| |
| @Test |
| public void testRequestNewCallFocusFailure() throws Exception { |
| // GIVEN |
| RequestNewActiveCallTransaction transaction = |
| new RequestNewActiveCallTransaction(mCallsManager, mMockCall1); |
| |
| // WHEN |
| when(mMockCall1.getState()).thenReturn(CallState.DISCONNECTING); |
| when(mCallsManager.getActiveCall()).thenReturn(null); |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(0)) |
| .requestNewCallFocusAndVerify( eq(mMockCall1), isA(OutcomeReceiver.class)); |
| } |
| |
| @Test |
| public void testTransactionalHoldActiveCallForNewCall() throws Exception { |
| // GIVEN |
| MaybeHoldCallForNewCallTransaction transaction = |
| new MaybeHoldCallForNewCallTransaction(mCallsManager, mMockCall1); |
| |
| // WHEN |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .transactionHoldPotentialActiveCallForNewCall(eq(mMockCall1), |
| isA(OutcomeReceiver.class)); |
| } |
| |
| @Test |
| public void testOutgoingCallTransaction() throws Exception { |
| // GIVEN |
| CallAttributes callAttributes = new CallAttributes.Builder(mHandle, |
| CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build(); |
| |
| OutgoingCallTransaction transaction = |
| new OutgoingCallTransaction(CALL_ID_1, mMockContext, callAttributes, mCallsManager); |
| |
| // WHEN |
| when(mMockContext.getOpPackageName()).thenReturn("testPackage"); |
| when(mMockContext.checkCallingPermission(android.Manifest.permission.CALL_PRIVILEGED)) |
| .thenReturn(PackageManager.PERMISSION_GRANTED); |
| when(mCallsManager.isOutgoingCallPermitted(callAttributes.getPhoneAccountHandle())) |
| .thenReturn(true); |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .startOutgoingCall(isA(Uri.class), |
| isA(PhoneAccountHandle.class), |
| isA(Bundle.class), |
| isA(UserHandle.class), |
| isA(Intent.class), |
| nullable(String.class)); |
| } |
| |
| @Test |
| public void testIncomingCallTransaction() throws Exception { |
| // GIVEN |
| CallAttributes callAttributes = new CallAttributes.Builder(mHandle, |
| CallAttributes.DIRECTION_INCOMING, TEST_NAME, TEST_URI).build(); |
| |
| IncomingCallTransaction transaction = |
| new IncomingCallTransaction(CALL_ID_1, callAttributes, mCallsManager); |
| |
| // WHEN |
| when(mCallsManager.isIncomingCallPermitted(callAttributes.getPhoneAccountHandle())) |
| .thenReturn(true); |
| transaction.processTransaction(null); |
| |
| // THEN |
| verify(mCallsManager, times(1)) |
| .processIncomingCallIntent(isA(PhoneAccountHandle.class), |
| isA(Bundle.class), |
| isA(Boolean.class)); |
| } |
| |
| /** |
| * This test verifies if the ConnectionService call is NOT transitioned to the desired call |
| * state (within timeout period), Telecom will disconnect the call. |
| */ |
| @SmallTest |
| @Test |
| public void testCallStateChangeTimesOut() |
| throws ExecutionException, InterruptedException, TimeoutException { |
| when(mFeatureFlags.transactionalCsVerifier()).thenReturn(true); |
| VerifyCallStateChangeTransaction t = new VerifyCallStateChangeTransaction(mCallsManager, |
| mMockCall1, CallState.ON_HOLD, true); |
| // WHEN |
| setupHoldableCall(); |
| |
| // simulate the transaction being processed and the CompletableFuture timing out |
| t.processTransaction(null); |
| CompletableFuture<Integer> timeoutFuture = t.getCallStateOrTimeoutResult(); |
| timeoutFuture.complete(VerifyCallStateChangeTransaction.FAILURE_CODE); |
| |
| // THEN |
| verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl()); |
| assertEquals(timeoutFuture.get().intValue(), VerifyCallStateChangeTransaction.FAILURE_CODE); |
| assertEquals(VoipCallTransactionResult.RESULT_FAILED, |
| t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult()); |
| verify(mMockCall1, atLeastOnce()).removeCallStateListener(any()); |
| verify(mCallsManager, times(1)).markCallAsDisconnected(eq(mMockCall1), any()); |
| verify(mCallsManager, times(1)).markCallAsRemoved(eq(mMockCall1)); |
| } |
| |
| /** |
| * This test verifies that when an application transitions a call to the requested state, |
| * Telecom does not disconnect the call and transaction completes successfully. |
| */ |
| @SmallTest |
| @Test |
| public void testCallStateIsSuccessfullyChanged() |
| throws ExecutionException, InterruptedException, TimeoutException { |
| when(mFeatureFlags.transactionalCsVerifier()).thenReturn(true); |
| VerifyCallStateChangeTransaction t = new VerifyCallStateChangeTransaction(mCallsManager, |
| mMockCall1, CallState.ON_HOLD, true); |
| // WHEN |
| setupHoldableCall(); |
| |
| // simulate the transaction being processed and the setOnHold() being called / state change |
| t.processTransaction(null); |
| when(mMockCall1.getState()).thenReturn(CallState.ON_HOLD); |
| t.getCallStateListenerImpl().onCallStateChanged(CallState.ON_HOLD); |
| |
| // THEN |
| verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl()); |
| assertEquals(t.getCallStateOrTimeoutResult().get().intValue(), |
| VerifyCallStateChangeTransaction.SUCCESS_CODE); |
| assertEquals(VoipCallTransactionResult.RESULT_SUCCEED, |
| t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult()); |
| verify(mMockCall1, atLeastOnce()).removeCallStateListener(any()); |
| verify(mCallsManager, never()).markCallAsDisconnected(eq(mMockCall1), any()); |
| verify(mCallsManager, never()).markCallAsRemoved(eq(mMockCall1)); |
| } |
| |
| private Call createSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState, String id) { |
| when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper); |
| |
| Call call = new Call(id, |
| mMockContext, |
| mCallsManager, |
| mLock, /* ConnectionServiceRepository */ |
| null, |
| mPhoneNumberUtilsAdapter, |
| TEST_ADDRESS, |
| null /* GatewayInfo */, |
| null /* ConnectionManagerAccount */, |
| targetPhoneAccount, |
| Call.CALL_DIRECTION_INCOMING, |
| false /* shouldAttachToExistingConnection*/, |
| false /* isConference */, |
| mClockProxy, |
| mToastFactory, |
| mFeatureFlags); |
| |
| Call callSpy = Mockito.spy(call); |
| |
| callSpy.setState(initialState, "manual set in test"); |
| |
| // Mocks some methods to not call the real method. |
| doNothing().when(callSpy).unhold(); |
| doNothing().when(callSpy).hold(); |
| doNothing().when(callSpy).disconnect(); |
| |
| return callSpy; |
| } |
| |
| private void setupHoldableCall(){ |
| when(mMockCall1.getState()).thenReturn(CallState.ACTIVE); |
| when(mMockCall1.getConnectionServiceWrapper()).thenReturn( |
| mock(ConnectionServiceWrapper.class)); |
| doNothing().when(mMockCall1).addCallStateListener(any()); |
| doReturn(true).when(mMockCall1).removeCallStateListener(any()); |
| } |
| } |