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