• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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