BluetoothRouteManagerTest.java revision 2afe9027824dbe6d88fb953045e12496b0da4cbc
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 17package com.android.server.telecom.tests; 18 19import android.bluetooth.BluetoothDevice; 20import android.content.ContentResolver; 21import android.os.Parcel; 22import android.telecom.Log; 23import android.test.suitebuilder.annotation.SmallTest; 24 25import com.android.internal.os.SomeArgs; 26import com.android.server.telecom.BluetoothHeadsetProxy; 27import com.android.server.telecom.TelecomSystem; 28import com.android.server.telecom.Timeouts; 29import com.android.server.telecom.bluetooth.BluetoothDeviceManager; 30import com.android.server.telecom.bluetooth.BluetoothRouteManager; 31 32import org.junit.Before; 33import org.junit.Test; 34import org.junit.runner.RunWith; 35import org.junit.runners.JUnit4; 36import org.mockito.Mock; 37 38import java.util.Arrays; 39import java.util.Objects; 40 41import static org.junit.Assert.assertEquals; 42import static org.mockito.ArgumentMatchers.nullable; 43import static org.mockito.Matchers.eq; 44import static org.mockito.Mockito.doAnswer; 45import static org.mockito.Mockito.reset; 46import static org.mockito.Mockito.times; 47import static org.mockito.Mockito.verify; 48import static org.mockito.Mockito.when; 49 50@RunWith(JUnit4.class) 51public class BluetoothRouteManagerTest extends TelecomTestCase { 52 private static final int TEST_TIMEOUT = 1000; 53 static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01"); 54 static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02"); 55 static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03"); 56 57 @Mock private BluetoothDeviceManager mDeviceManager; 58 @Mock private BluetoothHeadsetProxy mHeadsetProxy; 59 @Mock private Timeouts.Adapter mTimeoutsAdapter; 60 @Mock private BluetoothRouteManager.BluetoothStateListener mListener; 61 62 @Override 63 @Before 64 public void setUp() throws Exception { 65 super.setUp(); 66 } 67 68 @SmallTest 69 @Test 70 public void testConnectHfpRetryWhileNotConnected() { 71 BluetoothRouteManager sm = setupStateMachine( 72 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null); 73 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null); 74 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 75 nullable(ContentResolver.class))).thenReturn(0L); 76 when(mHeadsetProxy.connectAudio(nullable(String.class))).thenReturn(false); 77 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, null); 78 // Wait 3 times: for the first connection attempt, the retry attempt, 79 // the second retry, and once more to make sure there are only three attempts. 80 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 81 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 82 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 83 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 84 verify(mHeadsetProxy, times(3)).connectAudio(DEVICE1.getAddress()); 85 assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName()); 86 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 87 sm.quitNow(); 88 } 89 90 @SmallTest 91 @Test 92 public void testConnectHfpRetryWhileConnectedToAnotherDevice() { 93 BluetoothRouteManager sm = setupStateMachine( 94 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1); 95 setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null); 96 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 97 nullable(ContentResolver.class))).thenReturn(0L); 98 when(mHeadsetProxy.connectAudio(nullable(String.class))).thenReturn(false); 99 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress()); 100 // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction, 101 // so wait twice for the retry attempt, again to make sure there are only three attempts, 102 // and once more for good luck. 103 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 104 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 105 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 106 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 107 verify(mHeadsetProxy, times(3)).connectAudio(DEVICE2.getAddress()); 108 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 109 + ":" + DEVICE1.getAddress(), 110 sm.getCurrentState().getName()); 111 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 112 sm.quitNow(); 113 } 114 115 @SmallTest 116 @Test 117 public void testProperFallbackOrder1() { 118 // Device 1, 2, 3 are connected in that order. Device 1 is activated, then device 2. 119 // Disconnect device 2, verify fallback to device 1. Disconnect device 1, fallback to 120 // device 3. 121 BluetoothRouteManager sm = setupStateMachine( 122 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null); 123 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, null); 124 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress()); 125 verify(mHeadsetProxy, times(1)).connectAudio(DEVICE1.getAddress()); 126 127 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, DEVICE1); 128 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE1.getAddress()); 129 130 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress()); 131 verify(mHeadsetProxy, times(1)).connectAudio(DEVICE2.getAddress()); 132 133 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, DEVICE2); 134 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE2.getAddress()); 135 // Disconnect device 2 136 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE1}, null); 137 executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE2.getAddress()); 138 // Verify that we've fallen back to device 1 139 verify(mHeadsetProxy, times(2)).connectAudio(DEVICE1.getAddress()); 140 assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX 141 + ":" + DEVICE1.getAddress(), 142 sm.getCurrentState().getName()); 143 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE1}, DEVICE1); 144 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE1.getAddress()); 145 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 146 + ":" + DEVICE1.getAddress(), 147 sm.getCurrentState().getName()); 148 149 // Disconnect device 1 150 setupConnectedDevices(new BluetoothDevice[]{DEVICE3}, null); 151 executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE1.getAddress()); 152 // Verify that we've fallen back to device 3 153 verify(mHeadsetProxy, times(1)).connectAudio(DEVICE3.getAddress()); 154 assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX 155 + ":" + DEVICE3.getAddress(), 156 sm.getCurrentState().getName()); 157 setupConnectedDevices(new BluetoothDevice[]{DEVICE3}, DEVICE3); 158 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE3.getAddress()); 159 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 160 + ":" + DEVICE3.getAddress(), 161 sm.getCurrentState().getName()); 162 163 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 164 sm.quitNow(); 165 } 166 167 @SmallTest 168 @Test 169 public void testProperFallbackOrder2() { 170 // Device 1, 2, 3 are connected in that order. Device 3 is activated. 171 // Disconnect device 3, verify fallback to device 2. Disconnect device 2, fallback to 172 // device 1. 173 BluetoothRouteManager sm = setupStateMachine( 174 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null); 175 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, null); 176 executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE3.getAddress()); 177 verify(mHeadsetProxy, times(1)).connectAudio(DEVICE3.getAddress()); 178 179 setupConnectedDevices(new BluetoothDevice[]{DEVICE3, DEVICE2, DEVICE1}, DEVICE3); 180 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE3.getAddress()); 181 182 // Disconnect device 2 183 setupConnectedDevices(new BluetoothDevice[]{DEVICE2, DEVICE1}, null); 184 executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE3.getAddress()); 185 // Verify that we've fallen back to device 2 186 verify(mHeadsetProxy, times(1)).connectAudio(DEVICE2.getAddress()); 187 assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX 188 + ":" + DEVICE2.getAddress(), 189 sm.getCurrentState().getName()); 190 setupConnectedDevices(new BluetoothDevice[]{DEVICE2, DEVICE1}, DEVICE2); 191 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE2.getAddress()); 192 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 193 + ":" + DEVICE2.getAddress(), 194 sm.getCurrentState().getName()); 195 196 // Disconnect device 2 197 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null); 198 executeRoutingAction(sm, BluetoothRouteManager.LOST_DEVICE, DEVICE2.getAddress()); 199 // Verify that we've fallen back to device 1 200 verify(mHeadsetProxy, times(1)).connectAudio(DEVICE1.getAddress()); 201 assertEquals(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX 202 + ":" + DEVICE1.getAddress(), 203 sm.getCurrentState().getName()); 204 setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, DEVICE1); 205 executeRoutingAction(sm, BluetoothRouteManager.HFP_IS_ON, DEVICE1.getAddress()); 206 assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX 207 + ":" + DEVICE1.getAddress(), 208 sm.getCurrentState().getName()); 209 210 sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT); 211 sm.quitNow(); 212 } 213 214 private BluetoothRouteManager setupStateMachine(String initialState, 215 BluetoothDevice initialDevice) { 216 resetMocks(); 217 BluetoothRouteManager sm = new BluetoothRouteManager(mContext, 218 new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter); 219 sm.setListener(mListener); 220 sm.setInitialStateForTesting(initialState, initialDevice); 221 waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT); 222 resetMocks(); 223 return sm; 224 } 225 226 private void setupConnectedDevices(BluetoothDevice[] devices, BluetoothDevice activeDevice) { 227 when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length); 228 when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices)); 229 when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices)); 230 if (activeDevice != null) { 231 when(mHeadsetProxy.isAudioConnected(eq(activeDevice))).thenReturn(true); 232 } 233 doAnswer(invocation -> { 234 BluetoothDevice first = getFirstExcluding(devices, 235 (String) invocation.getArguments()[0]); 236 return first == null ? null : first.getAddress(); 237 }).when(mDeviceManager).getMostRecentlyConnectedDevice(nullable(String.class)); 238 } 239 240 static void executeRoutingAction(BluetoothRouteManager brm, int message, String 241 device) { 242 SomeArgs args = SomeArgs.obtain(); 243 args.arg1 = Log.createSubsession(); 244 args.arg2 = device; 245 brm.sendMessage(message, args); 246 waitForHandlerAction(brm.getHandler(), TEST_TIMEOUT); 247 } 248 249 public static BluetoothDevice makeBluetoothDevice(String address) { 250 Parcel p1 = Parcel.obtain(); 251 p1.writeString(address); 252 p1.setDataPosition(0); 253 BluetoothDevice device = BluetoothDevice.CREATOR.createFromParcel(p1); 254 p1.recycle(); 255 return device; 256 } 257 258 private void resetMocks() { 259 reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter); 260 when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy); 261 when(mHeadsetProxy.connectAudio(nullable(String.class))).thenReturn(true); 262 when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis( 263 nullable(ContentResolver.class))).thenReturn(100000L); 264 when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis( 265 nullable(ContentResolver.class))).thenReturn(100000L); 266 } 267 268 private static BluetoothDevice getFirstExcluding( 269 BluetoothDevice[] devices, String excludeAddress) { 270 for (BluetoothDevice x : devices) { 271 if (!Objects.equals(excludeAddress, x.getAddress())) { 272 return x; 273 } 274 } 275 return null; 276 } 277} 278