1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.telecom.tests; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothHeadset; 21 import android.bluetooth.BluetoothHearingAid; 22 import android.content.ContentResolver; 23 import android.os.Parcel; 24 import android.telecom.Log; 25 import android.test.suitebuilder.annotation.SmallTest; 26 27 import com.android.internal.os.SomeArgs; 28 import com.android.server.telecom.BluetoothHeadsetProxy; 29 import com.android.server.telecom.TelecomSystem; 30 import com.android.server.telecom.Timeouts; 31 import com.android.server.telecom.bluetooth.BluetoothDeviceManager; 32 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 33 34 import org.junit.Before; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.junit.runners.JUnit4; 38 import org.mockito.Mock; 39 40 import java.util.Arrays; 41 import java.util.List; 42 import java.util.stream.Collectors; 43 import java.util.stream.Stream; 44 45 import static org.junit.Assert.assertEquals; 46 import static org.mockito.ArgumentMatchers.any; 47 import static org.mockito.ArgumentMatchers.nullable; 48 import static org.mockito.Matchers.eq; 49 import static org.mockito.Mockito.reset; 50 import static org.mockito.Mockito.times; 51 import static org.mockito.Mockito.verify; 52 import static org.mockito.Mockito.when; 53 54 @RunWith(JUnit4.class) 55 public class BluetoothRouteManagerTest extends TelecomTestCase { 56 private static final int TEST_TIMEOUT = 1000; 57 static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01"); 58 static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02"); 59 static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03"); 60 static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04"); 61 62 @Mock private BluetoothDeviceManager mDeviceManager; 63 @Mock private BluetoothHeadsetProxy mHeadsetProxy; 64 @Mock private BluetoothHearingAid mBluetoothHearingAid; 65 @Mock private Timeouts.Adapter mTimeoutsAdapter; 66 @Mock private BluetoothRouteManager.BluetoothStateListener mListener; 67 68 @Override 69 @Before setUp()70 public void setUp() throws Exception { 71 super.setUp(); 72 } 73 74 @SmallTest 75 @Test testConnectHfpRetryWhileNotConnected()76 public void testConnectHfpRetryWhileNotConnected() { 77 BluetoothRouteManager sm = setupStateMachine( 78 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null); 79 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null); 80 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 81 nullable(ContentResolver.class))).thenReturn(0L); 82 when(mHeadsetProxy.connectAudio()).thenReturn(false); 83 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress()); 84 // Wait 3 times: for the first connection attempt, the retry attempt, 85 // the second retry, and once more to make sure there are only three attempts. 86 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 87 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 88 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 89 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 90 verifyConnectionAttempt(DEVICE1, 3); 91 assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName()); 92 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 93 sm.quitNow(); 94 } 95 96 @SmallTest 97 @Test testAmbiguousActiveDevice()98 public void testAmbiguousActiveDevice() { 99 BluetoothRouteManager sm = setupStateMachine( 100 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 101 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, 102 new BluetoothDevice[]{HEARING_AID_DEVICE}, DEVICE1, HEARING_AID_DEVICE); 103 sm.onActiveDeviceChanged(DEVICE1, false); 104 sm.onActiveDeviceChanged(HEARING_AID_DEVICE, true); 105 executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress()); 106 107 verifyConnectionAttempt(HEARING_AID_DEVICE, 0); 108 verifyConnectionAttempt(DEVICE1, 0); 109 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 110 + ":" + HEARING_AID_DEVICE.getAddress(), 111 sm.getCurrentState().getName()); 112 sm.quitNow(); 113 } 114 115 @SmallTest 116 @Test testAudioOnDeviceWithScoOffActiveDevice()117 public void testAudioOnDeviceWithScoOffActiveDevice() { 118 BluetoothRouteManager sm = setupStateMachine( 119 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 120 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, DEVICE1, null); 121 when(mHeadsetProxy.getAudioState(DEVICE1)) 122 .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 123 executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress()); 124 125 verifyConnectionAttempt(DEVICE1, 0); 126 assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, 127 sm.getCurrentState().getName()); 128 sm.quitNow(); 129 } 130 131 @SmallTest 132 @Test testConnectHfpRetryWhileConnectedToAnotherDevice()133 public void testConnectHfpRetryWhileConnectedToAnotherDevice() { 134 BluetoothRouteManager sm = setupStateMachine( 135 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 136 setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null); 137 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 138 nullable(ContentResolver.class))).thenReturn(0L); 139 when(mHeadsetProxy.connectAudio()).thenReturn(false); 140 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress()); 141 // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction, 142 // so wait twice for the retry attempt, again to make sure there are only three attempts, 143 // and once more for good luck. 144 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 145 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 146 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 147 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 148 verifyConnectionAttempt(DEVICE2, 3); 149 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 150 + ":" + DEVICE1.getAddress(), 151 sm.getCurrentState().getName()); 152 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 153 sm.quitNow(); 154 } 155 setupStateMachine(String initialState, BluetoothDevice initialDevice)156 private BluetoothRouteManager setupStateMachine(String initialState, 157 BluetoothDevice initialDevice) { 158 resetMocks(); 159 BluetoothRouteManager sm = new BluetoothRouteManager(mContext, 160 new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter); 161 sm.setListener(mListener); 162 sm.setInitialStateForTesting(initialState, initialDevice); 163 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 164 resetMocks(); 165 return sm; 166 } 167 setupConnectedDevices(BluetoothDevice[] hfpDevices, BluetoothDevice[] hearingAidDevices, BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice)168 private void setupConnectedDevices(BluetoothDevice[] hfpDevices, 169 BluetoothDevice[] hearingAidDevices, 170 BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice) { 171 if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{}; 172 if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{}; 173 174 when(mDeviceManager.getNumConnectedDevices()).thenReturn( 175 hfpDevices.length + hearingAidDevices.length); 176 List<BluetoothDevice> allDevices = Stream.concat( 177 Arrays.stream(hfpDevices), Arrays.stream(hearingAidDevices)) 178 .collect(Collectors.toList()); 179 180 when(mDeviceManager.getConnectedDevices()).thenReturn(allDevices); 181 when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices)); 182 when(mHeadsetProxy.getActiveDevice()).thenReturn(hfpActiveDevice); 183 when(mHeadsetProxy.getAudioState(hfpActiveDevice)) 184 .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); 185 186 when(mBluetoothHearingAid.getConnectedDevices()) 187 .thenReturn(Arrays.asList(hearingAidDevices)); 188 when(mBluetoothHearingAid.getActiveDevices()) 189 .thenReturn(Arrays.asList(hearingAidActiveDevice, null)); 190 } 191 executeRoutingAction(BluetoothRouteManager brm, int message, String device)192 static void executeRoutingAction(BluetoothRouteManager brm, int message, String 193 device) { 194 SomeArgs args = SomeArgs.obtain(); 195 args.arg1 = Log.createSubsession(); 196 args.arg2 = device; 197 brm.sendMessage(message, args); 198 waitForHandlerAction(brm.getHandler(), TEST_TIMEOUT); 199 } 200 makeBluetoothDevice(String address)201 public static BluetoothDevice makeBluetoothDevice(String address) { 202 Parcel p1 = Parcel.obtain(); 203 p1.writeString(address); 204 p1.setDataPosition(0); 205 BluetoothDevice device = BluetoothDevice.CREATOR.createFromParcel(p1); 206 p1.recycle(); 207 return device; 208 } 209 resetMocks()210 private void resetMocks() { 211 reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter); 212 when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy); 213 when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid); 214 when(mHeadsetProxy.connectAudio()).thenReturn(true); 215 when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true); 216 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 217 nullable(ContentResolver.class))).thenReturn(100000L); 218 when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis( 219 nullable(ContentResolver.class))).thenReturn(100000L); 220 } 221 verifyConnectionAttempt(BluetoothDevice device, int numTimes)222 private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) { 223 verify(mDeviceManager, times(numTimes)).connectAudio(device.getAddress()); 224 } 225 } 226