| /* |
| * Copyright (C) 2015 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 android.bluetooth.BluetoothDevice; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.media.AudioDeviceInfo; |
| import android.media.AudioManager; |
| import android.media.IAudioService; |
| import android.os.HandlerThread; |
| import android.telecom.CallAudioState; |
| import android.test.suitebuilder.annotation.MediumTest; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import com.android.server.telecom.bluetooth.BluetoothRouteManager; |
| import com.android.server.telecom.Call; |
| import com.android.server.telecom.CallAudioRouteStateMachine; |
| import com.android.server.telecom.CallsManager; |
| import com.android.server.telecom.ConnectionServiceWrapper; |
| import com.android.server.telecom.CallAudioManager; |
| import com.android.server.telecom.StatusBarNotifier; |
| import com.android.server.telecom.TelecomSystem; |
| import com.android.server.telecom.WiredHeadsetManager; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.invocation.InvocationOnMock; |
| import org.mockito.stubbing.Answer; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.nullable; |
| import static org.mockito.Matchers.any; |
| import static org.mockito.Matchers.same; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| @RunWith(JUnit4.class) |
| public class CallAudioRouteStateMachineTest extends TelecomTestCase { |
| |
| private static final BluetoothDevice bluetoothDevice1 = |
| BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01"); |
| private static final BluetoothDevice bluetoothDevice2 = |
| BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:02"); |
| private static final BluetoothDevice bluetoothDevice3 = |
| BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03"); |
| |
| @Mock CallsManager mockCallsManager; |
| @Mock BluetoothRouteManager mockBluetoothRouteManager; |
| @Mock IAudioService mockAudioService; |
| @Mock ConnectionServiceWrapper mockConnectionServiceWrapper; |
| @Mock WiredHeadsetManager mockWiredHeadsetManager; |
| @Mock StatusBarNotifier mockStatusBarNotifier; |
| @Mock Call fakeCall; |
| @Mock CallAudioManager mockCallAudioManager; |
| |
| private CallAudioManager.AudioServiceFactory mAudioServiceFactory; |
| private static final int TEST_TIMEOUT = 500; |
| private AudioManager mockAudioManager; |
| private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { }; |
| private HandlerThread mThreadHandler; |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| MockitoAnnotations.initMocks(this); |
| mThreadHandler = new HandlerThread("CallAudioRouteStateMachineTest"); |
| mThreadHandler.start(); |
| mContext = mComponentContextFixture.getTestDouble().getApplicationContext(); |
| mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); |
| |
| mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() { |
| @Override |
| public IAudioService getAudioService() { |
| return mockAudioService; |
| } |
| }; |
| |
| when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall); |
| when(mockCallsManager.getLock()).thenReturn(mLock); |
| when(mockCallsManager.hasVideoCall()).thenReturn(false); |
| when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper); |
| when(fakeCall.isAlive()).thenReturn(true); |
| when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL); |
| |
| doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class), |
| any(CallAudioState.class)); |
| } |
| |
| @Override |
| @After |
| public void tearDown() throws Exception { |
| mThreadHandler.quit(); |
| mThreadHandler.join(); |
| super.tearDown(); |
| } |
| |
| @SmallTest |
| @Test |
| public void testEarpieceAutodetect() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT, |
| mThreadHandler.getLooper()); |
| |
| // Since we don't know if we're on a platform with an earpiece or not, all we can do |
| // is ensure the stateMachine construction didn't fail. But at least we exercised the |
| // autodetection code... |
| assertNotNull(stateMachine); |
| } |
| |
| @MediumTest |
| @Test |
| public void testStreamRingMuteChange() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); |
| stateMachine.initialize(initState); |
| |
| // Make sure we register a receiver for the STREAM_MUTE_CHANGED_ACTION so we can see if the |
| // ring stream unmutes. |
| ArgumentCaptor<BroadcastReceiver> brCaptor = ArgumentCaptor.forClass( |
| BroadcastReceiver.class); |
| ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class); |
| verify(mContext, times(3)).registerReceiver(brCaptor.capture(), filterCaptor.capture()); |
| boolean foundValid = false; |
| for (int ix = 0; ix < brCaptor.getAllValues().size(); ix++) { |
| BroadcastReceiver receiver = brCaptor.getAllValues().get(ix); |
| IntentFilter filter = filterCaptor.getAllValues().get(ix); |
| if (!filter.hasAction(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { |
| continue; |
| } |
| |
| // Fake out a call to the broadcast receiver and make sure we call into audio manager |
| // to trigger re-evaluation of ringing. |
| Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); |
| intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false); |
| intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_RING); |
| receiver.onReceive(mContext, intent); |
| verify(mockCallAudioManager).onRingerModeChange(); |
| foundValid = true; |
| } |
| assertTrue(foundValid); |
| } |
| |
| @MediumTest |
| @Test |
| public void testSpeakerPersistence() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true); |
| doAnswer(new Answer() { |
| @Override |
| public Object answer(InvocationOnMock invocation) throws Throwable { |
| Object[] args = invocation.getArguments(); |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn((Boolean) args[0]); |
| return null; |
| } |
| }).when(mockAudioManager).setSpeakerphoneOn(any(Boolean.class)); |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); |
| stateMachine.initialize(initState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET); |
| CallAudioState expectedMiddleState = new CallAudioState(false, |
| CallAudioState.ROUTE_WIRED_HEADSET, |
| CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verifyNewSystemCallAudioState(initState, expectedMiddleState); |
| resetMocks(); |
| |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verifyNewSystemCallAudioState(expectedMiddleState, initState); |
| } |
| |
| @MediumTest |
| @Test |
| public void testUserBluetoothSwitchOff() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true); |
| |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); |
| stateMachine.initialize(initState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, |
| CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE); |
| CallAudioState expectedEndState = new CallAudioState(false, |
| CallAudioState.ROUTE_EARPIECE, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); |
| |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verifyNewSystemCallAudioState(initState, expectedEndState); |
| resetMocks(); |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE); |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); |
| |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState()); |
| } |
| |
| @MediumTest |
| @Test |
| public void testUserBluetoothSwitchOffAndOnAgain() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1); |
| |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices); |
| |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); |
| stateMachine.initialize(initState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); |
| |
| // Switch off the BT route explicitly. |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE, |
| CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE); |
| CallAudioState expectedMidState = new CallAudioState(false, |
| CallAudioState.ROUTE_EARPIECE, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, |
| null, availableDevices); |
| |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verifyNewSystemCallAudioState(initState, expectedMidState); |
| // clear out the handler state before resetting mocks in order to avoid introducing a |
| // CallAudioState that has a null list of supported BT devices |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| resetMocks(); |
| |
| // Now, switch back to BT explicitly |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices); |
| doAnswer(invocation -> { |
| when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice()) |
| .thenReturn(bluetoothDevice1); |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); |
| return null; |
| }).when(mockBluetoothRouteManager).connectBluetoothAudio(nullable(String.class)); |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH); |
| CallAudioState expectedEndState = new CallAudioState(false, |
| CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, |
| bluetoothDevice1, availableDevices); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| // second wait needed for the BT_AUDIO_CONNECTED message |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verifyNewSystemCallAudioState(expectedMidState, expectedEndState); |
| |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE); |
| when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice()) |
| .thenReturn(null); |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); |
| |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| // second wait needed for the BT_AUDIO_CONNECTED message |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| // Verify that we're still on bluetooth. |
| assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState()); |
| } |
| |
| @MediumTest |
| @Test |
| public void testBluetoothRinging() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.getConnectedDevices()) |
| .thenReturn(Collections.singletonList(bluetoothDevice1)); |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false); |
| |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH); |
| stateMachine.initialize(initState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.RINGING_FOCUS); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| |
| verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class)); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(nullable(String.class)); |
| } |
| |
| @MediumTest |
| @Test |
| public void testConnectBluetoothDuringRinging() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| setInBandRing(false); |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(false); |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false); |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, |
| CallAudioState.ROUTE_EARPIECE); |
| stateMachine.initialize(initState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.RINGING_FOCUS); |
| // Wait for the state machine to finish transiting to ActiveEarpiece before hooking up |
| // bluetooth mocks |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.getConnectedDevices()) |
| .thenReturn(Collections.singletonList(bluetoothDevice1)); |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED); |
| stateMachine.sendMessageWithSessionInfo( |
| CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| |
| verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null); |
| CallAudioState expectedEndState = new CallAudioState(false, |
| CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, |
| null, Collections.singletonList(bluetoothDevice1)); |
| verifyNewSystemCallAudioState(initState, expectedEndState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null); |
| |
| when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice()) |
| .thenReturn(bluetoothDevice1); |
| stateMachine.sendMessage(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| verify(mockCallAudioManager, times(1)).onRingerModeChange(); |
| } |
| |
| @SmallTest |
| @Test |
| public void testConnectSpecificBluetoothDevice() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| List<BluetoothDevice> availableDevices = |
| Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3); |
| |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices); |
| doAnswer(invocation -> { |
| when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice()) |
| .thenReturn(bluetoothDevice2); |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED); |
| return null; |
| }).when(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress()); |
| |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, null, |
| availableDevices); |
| stateMachine.initialize(initState); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, |
| 0, bluetoothDevice2.getAddress()); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| |
| verify(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress()); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| CallAudioState expectedEndState = new CallAudioState(false, |
| CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, |
| bluetoothDevice2, |
| availableDevices); |
| |
| verifyNewSystemCallAudioState(initState, expectedEndState); |
| } |
| |
| @SmallTest |
| @Test |
| public void testDockWhenInQuiescentState() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false); |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); |
| stateMachine.initialize(initState); |
| |
| // Raise a dock connect event. |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_DOCK); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| assertTrue(!stateMachine.isInActiveState()); |
| verify(mockAudioManager, never()).setSpeakerphoneOn(eq(true)); |
| |
| // Raise a dock disconnect event. |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.DISCONNECT_DOCK); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| assertTrue(!stateMachine.isInActiveState()); |
| verify(mockAudioManager, never()).setSpeakerphoneOn(eq(false)); |
| } |
| |
| @SmallTest |
| @Test |
| public void testFocusChangeFromQuiescentSpeaker() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false); |
| |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); |
| stateMachine.initialize(initState); |
| |
| // Switch to active, pretending that a call came in. |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| |
| // Make sure that we've successfully switched to the active speaker route and that we've |
| // called setSpeakerOn |
| assertTrue(stateMachine.isInActiveState()); |
| ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass( |
| AudioDeviceInfo.class); |
| verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture()); |
| assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType()); |
| } |
| |
| @SmallTest |
| @Test |
| public void testFocusChangeWithAlreadyActiveBtDevice() { |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.setCallAudioManager(mockCallAudioManager); |
| List<BluetoothDevice> availableDevices = |
| Arrays.asList(bluetoothDevice1, bluetoothDevice2); |
| |
| // Set up a state where there's an HFP connected bluetooth device already. |
| when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false); |
| when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices); |
| when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice()) |
| .thenReturn(bluetoothDevice1); |
| |
| // We want to be in the QuiescentBluetoothRoute because there's no call yet |
| CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, |
| bluetoothDevice1, availableDevices); |
| stateMachine.initialize(initState); |
| |
| // Switch to active, pretending that a call came in. |
| stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS, |
| CallAudioRouteStateMachine.ACTIVE_FOCUS); |
| waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT); |
| |
| // Make sure that we've successfully switched to the active BT route and that we've |
| // called connectAudio on the right device. |
| verify(mockBluetoothRouteManager, atLeastOnce()) |
| .connectBluetoothAudio(eq(bluetoothDevice1.getAddress())); |
| assertTrue(stateMachine.isInActiveState()); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithEarpieceNoHeadsetNoBluetooth() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithEarpieceAndHeadsetNoBluetooth() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET, |
| CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithEarpieceAndHeadsetAndBluetooth() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER |
| | CallAudioState.ROUTE_BLUETOOTH); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithEarpieceAndBluetoothNoHeadset() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER |
| | CallAudioState.ROUTE_BLUETOOTH); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithNoEarpieceNoHeadsetNoBluetooth() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, |
| CallAudioState.ROUTE_SPEAKER); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithHeadsetNoBluetoothNoEarpiece() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET, |
| CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithHeadsetAndBluetoothNoEarpiece() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER |
| | CallAudioState.ROUTE_BLUETOOTH); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithBluetoothNoHeadsetNoEarpiece() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, |
| CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH); |
| initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED); |
| } |
| |
| @SmallTest |
| @Test |
| public void testInitializationWithAvailableButInactiveBtDevice() { |
| CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, |
| CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH |
| | CallAudioState.ROUTE_EARPIECE); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true); |
| when(mockBluetoothRouteManager.hasBtActiveDevice()).thenReturn(false); |
| |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED, |
| mThreadHandler.getLooper()); |
| stateMachine.initialize(); |
| assertEquals(expectedState, stateMachine.getCurrentCallAudioState()); |
| } |
| |
| private void initializationTestHelper(CallAudioState expectedState, |
| int earpieceControl) { |
| when(mockWiredHeadsetManager.isPluggedIn()).thenReturn( |
| (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_WIRED_HEADSET) != 0); |
| when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn( |
| (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0); |
| when(mockBluetoothRouteManager.hasBtActiveDevice()).thenReturn( |
| (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0); |
| |
| CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine( |
| mContext, |
| mockCallsManager, |
| mockBluetoothRouteManager, |
| mockWiredHeadsetManager, |
| mockStatusBarNotifier, |
| mAudioServiceFactory, |
| earpieceControl, |
| mThreadHandler.getLooper()); |
| stateMachine.initialize(); |
| assertEquals(expectedState, stateMachine.getCurrentCallAudioState()); |
| } |
| |
| private void verifyNewSystemCallAudioState(CallAudioState expectedOldState, |
| CallAudioState expectedNewState) { |
| ArgumentCaptor<CallAudioState> oldStateCaptor = ArgumentCaptor.forClass( |
| CallAudioState.class); |
| ArgumentCaptor<CallAudioState> newStateCaptor1 = ArgumentCaptor.forClass( |
| CallAudioState.class); |
| ArgumentCaptor<CallAudioState> newStateCaptor2 = ArgumentCaptor.forClass( |
| CallAudioState.class); |
| verify(mockCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged( |
| oldStateCaptor.capture(), newStateCaptor1.capture()); |
| verify(mockConnectionServiceWrapper, timeout(TEST_TIMEOUT).atLeastOnce()) |
| .onCallAudioStateChanged(same(fakeCall), newStateCaptor2.capture()); |
| |
| assertTrue(oldStateCaptor.getAllValues().get(0).equals(expectedOldState)); |
| assertTrue(newStateCaptor1.getValue().equals(expectedNewState)); |
| assertTrue(newStateCaptor2.getValue().equals(expectedNewState)); |
| } |
| |
| private void setInBandRing(boolean enabled) { |
| when(mockBluetoothRouteManager.isInbandRingingEnabled()).thenReturn(enabled); |
| } |
| |
| private void resetMocks() { |
| reset(mockAudioManager, mockBluetoothRouteManager, mockCallsManager, |
| mockConnectionServiceWrapper); |
| fakeCall = mock(Call.class); |
| when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall); |
| when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper); |
| when(fakeCall.isAlive()).thenReturn(true); |
| when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL); |
| when(mockCallsManager.getLock()).thenReturn(mLock); |
| doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class), |
| any(CallAudioState.class)); |
| } |
| } |