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.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadset; 22 import android.bluetooth.BluetoothHearingAid; 23 import android.bluetooth.BluetoothLeAudio; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.BluetoothStatusCodes; 26 import android.content.ContentResolver; 27 import android.os.Parcel; 28 import android.telecom.Log; 29 import android.test.suitebuilder.annotation.SmallTest; 30 31 import com.android.internal.os.SomeArgs; 32 import com.android.server.telecom.TelecomSystem; 33 import com.android.server.telecom.Timeouts; 34 import com.android.server.telecom.bluetooth.BluetoothDeviceManager; 35 import com.android.server.telecom.bluetooth.BluetoothRouteManager; 36 37 import org.junit.After; 38 import org.junit.Before; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 import org.junit.runners.JUnit4; 42 import org.mockito.Mock; 43 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.stream.Collectors; 47 import java.util.stream.Stream; 48 49 import static org.junit.Assert.assertEquals; 50 import static org.mockito.ArgumentMatchers.any; 51 import static org.mockito.ArgumentMatchers.anyBoolean; 52 import static org.mockito.ArgumentMatchers.nullable; 53 import static org.mockito.Matchers.eq; 54 import static org.mockito.Mockito.reset; 55 import static org.mockito.Mockito.times; 56 import static org.mockito.Mockito.verify; 57 import static org.mockito.Mockito.when; 58 59 @RunWith(JUnit4.class) 60 public class BluetoothRouteManagerTest extends TelecomTestCase { 61 private static final int TEST_TIMEOUT = 1000; 62 static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01"); 63 static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02"); 64 static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03"); 65 static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04"); 66 67 @Mock private BluetoothAdapter mBluetoothAdapter; 68 @Mock private BluetoothDeviceManager mDeviceManager; 69 @Mock private BluetoothHeadset mBluetoothHeadset; 70 @Mock private BluetoothHearingAid mBluetoothHearingAid; 71 @Mock private BluetoothLeAudio mBluetoothLeAudio; 72 @Mock private Timeouts.Adapter mTimeoutsAdapter; 73 @Mock private BluetoothRouteManager.BluetoothStateListener mListener; 74 75 @Override 76 @Before setUp()77 public void setUp() throws Exception { 78 super.setUp(); 79 } 80 81 @Override 82 @After tearDown()83 public void tearDown() throws Exception { 84 super.tearDown(); 85 } 86 87 @SmallTest 88 @Test testConnectBtRetryWhileNotConnected()89 public void testConnectBtRetryWhileNotConnected() { 90 BluetoothRouteManager sm = setupStateMachine( 91 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null); 92 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null, null, null); 93 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 94 nullable(ContentResolver.class))).thenReturn(0L); 95 when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN); 96 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, DEVICE1.getAddress()); 97 // Wait 3 times: for the first connection attempt, the retry attempt, 98 // the second retry, and once more to make sure there are only three attempts. 99 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 100 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 101 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 102 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 103 verifyConnectionAttempt(DEVICE1, 3); 104 assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName()); 105 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 106 sm.quitNow(); 107 } 108 109 @SmallTest 110 @Test testAmbiguousActiveDevice()111 public void testAmbiguousActiveDevice() { 112 BluetoothRouteManager sm = setupStateMachine( 113 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 114 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, 115 new BluetoothDevice[]{HEARING_AID_DEVICE}, new BluetoothDevice[]{DEVICE2}, 116 DEVICE1, HEARING_AID_DEVICE, DEVICE2); 117 sm.onActiveDeviceChanged(DEVICE1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET); 118 sm.onActiveDeviceChanged(DEVICE2, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO); 119 sm.onActiveDeviceChanged(HEARING_AID_DEVICE, 120 BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID); 121 executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress()); 122 123 verifyConnectionAttempt(HEARING_AID_DEVICE, 0); 124 verifyConnectionAttempt(DEVICE1, 0); 125 verifyConnectionAttempt(DEVICE2, 0); 126 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 127 + ":" + DEVICE1.getAddress(), 128 sm.getCurrentState().getName()); 129 sm.quitNow(); 130 } 131 132 @SmallTest 133 @Test testAudioOnDeviceWithScoOffActiveDevice()134 public void testAudioOnDeviceWithScoOffActiveDevice() { 135 BluetoothRouteManager sm = setupStateMachine( 136 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 137 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, DEVICE1, null, null); 138 when(mBluetoothHeadset.getAudioState(DEVICE1)) 139 .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); 140 executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress()); 141 142 verifyConnectionAttempt(DEVICE1, 0); 143 assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, 144 sm.getCurrentState().getName()); 145 sm.quitNow(); 146 } 147 148 @SmallTest 149 @Test testConnectBtRetryWhileConnectedToAnotherDevice()150 public void testConnectBtRetryWhileConnectedToAnotherDevice() { 151 BluetoothRouteManager sm = setupStateMachine( 152 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 153 setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null, 154 null); 155 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 156 nullable(ContentResolver.class))).thenReturn(0L); 157 when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN); 158 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, DEVICE2.getAddress()); 159 // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction, 160 // so wait twice for the retry attempt, again to make sure there are only three attempts, 161 // and once more for good luck. 162 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 163 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 164 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 165 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 166 verifyConnectionAttempt(DEVICE2, 3); 167 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 168 + ":" + DEVICE1.getAddress(), 169 sm.getCurrentState().getName()); 170 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 171 sm.quitNow(); 172 } 173 setupStateMachine(String initialState, BluetoothDevice initialDevice)174 private BluetoothRouteManager setupStateMachine(String initialState, 175 BluetoothDevice initialDevice) { 176 resetMocks(); 177 BluetoothRouteManager sm = new BluetoothRouteManager(mContext, 178 new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter); 179 sm.setListener(mListener); 180 sm.setInitialStateForTesting(initialState, initialDevice); 181 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 182 resetMocks(); 183 return sm; 184 } 185 setupConnectedDevices(BluetoothDevice[] hfpDevices, BluetoothDevice[] hearingAidDevices, BluetoothDevice[] leAudioDevices, BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice, BluetoothDevice leAudioDevice)186 private void setupConnectedDevices(BluetoothDevice[] hfpDevices, 187 BluetoothDevice[] hearingAidDevices, BluetoothDevice[] leAudioDevices, 188 BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice, 189 BluetoothDevice leAudioDevice) { 190 if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{}; 191 if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{}; 192 if (leAudioDevice == null) leAudioDevices = new BluetoothDevice[]{}; 193 194 when(mDeviceManager.getNumConnectedDevices()).thenReturn( 195 hfpDevices.length + hearingAidDevices.length + leAudioDevices.length); 196 List<BluetoothDevice> allDevices = Stream.of( 197 Arrays.stream(hfpDevices), Arrays.stream(hearingAidDevices), 198 Arrays.stream(leAudioDevices)).flatMap(i -> i).collect(Collectors.toList()); 199 200 when(mDeviceManager.getConnectedDevices()).thenReturn(allDevices); 201 when(mBluetoothHeadset.getConnectedDevices()).thenReturn(Arrays.asList(hfpDevices)); 202 when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEADSET))) 203 .thenReturn(Arrays.asList(hfpActiveDevice)); 204 when(mBluetoothHeadset.getAudioState(hfpActiveDevice)) 205 .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); 206 207 when(mBluetoothHearingAid.getConnectedDevices()) 208 .thenReturn(Arrays.asList(hearingAidDevices)); 209 when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID))) 210 .thenReturn(Arrays.asList(hearingAidActiveDevice, null)); 211 when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO))) 212 .thenReturn(Arrays.asList(leAudioDevice, null)); 213 } 214 executeRoutingAction(BluetoothRouteManager brm, int message, String device)215 static void executeRoutingAction(BluetoothRouteManager brm, int message, String 216 device) { 217 SomeArgs args = SomeArgs.obtain(); 218 args.arg1 = Log.createSubsession(); 219 args.arg2 = device; 220 brm.sendMessage(message, args); 221 waitForHandlerAction(brm.getHandler(), TEST_TIMEOUT); 222 } 223 makeBluetoothDevice(String address)224 public static BluetoothDevice makeBluetoothDevice(String address) { 225 Parcel p1 = Parcel.obtain(); 226 p1.writeString(address); 227 p1.setDataPosition(0); 228 BluetoothDevice device = BluetoothDevice.CREATOR.createFromParcel(p1); 229 p1.recycle(); 230 return device; 231 } 232 resetMocks()233 private void resetMocks() { 234 reset(mDeviceManager, mListener, mBluetoothHeadset, mTimeoutsAdapter); 235 when(mDeviceManager.getBluetoothHeadset()).thenReturn(mBluetoothHeadset); 236 when(mDeviceManager.getBluetoothHearingAid()).thenReturn(mBluetoothHearingAid); 237 when(mDeviceManager.getBluetoothAdapter()).thenReturn(mBluetoothAdapter); 238 when(mDeviceManager.getLeAudioService()).thenReturn(mBluetoothLeAudio); 239 when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.SUCCESS); 240 when(mBluetoothAdapter.setActiveDevice(nullable(BluetoothDevice.class), 241 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true); 242 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 243 nullable(ContentResolver.class))).thenReturn(100000L); 244 when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis( 245 nullable(ContentResolver.class))).thenReturn(100000L); 246 } 247 verifyConnectionAttempt(BluetoothDevice device, int numTimes)248 private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) { 249 verify(mDeviceManager, times(numTimes)).connectAudio(eq(device.getAddress()), 250 anyBoolean()); 251 } 252 } 253